changeset 2421:d141e1bfdd63 HEAD

We never do blocking reads/writes to network anymore. Changed imap and pop3 processes to use a single I/O loop. Not much tested yet, and currently LIST/LSUB may eat too much memory and APPEND eats all CPU.
author Timo Sirainen <tss@iki.fi>
date Sun, 15 Aug 2004 06:40:30 +0300
parents 90f25ed2dcef
children d5aa32e5a9bc
files src/auth/auth-client-connection.c src/auth/auth-master-connection.c src/auth/password-scheme.c src/imap-login/client-authenticate.c src/imap-login/client.c src/imap/Makefile.am src/imap/client.c src/imap/client.h src/imap/cmd-append.c src/imap/cmd-fetch.c src/imap/cmd-idle.c src/imap/cmd-search.c src/imap/common.h src/imap/imap-fetch-body-section.c src/imap/imap-fetch-body.c src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-messageset.c src/imap/imap-messageset.h src/imap/imap-search.c src/imap/imap-search.h src/imap/imap-thread.c src/imap/mail-storage-callbacks.c src/imap/main.c src/lib-auth/auth-server-connection.c src/lib-auth/auth-server-request.c src/lib-mail/istream-header-filter.c src/lib-mail/istream-header-filter.h src/lib-mail/message-send.c src/lib-mail/message-send.h src/lib-storage/index/index-mail-headers.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-storage.c src/lib-storage/index/maildir/maildir-save.c src/lib-storage/index/mbox/istream-raw-mbox.c src/lib-storage/index/mbox/mbox-lock.c src/lib-storage/index/mbox/mbox-save.c src/lib-storage/mail-storage.h src/lib/Makefile.am src/lib/alarm-hup.c src/lib/alarm-hup.h src/lib/file-lock.c src/lib/iostream-internal.h src/lib/iostream.c src/lib/istream-data.c src/lib/istream-file.c src/lib/istream-limit.c src/lib/istream-mmap.c src/lib/istream.c src/lib/istream.h src/lib/lib.c src/lib/ostream-file.c src/lib/ostream-internal.h src/lib/ostream.c src/lib/ostream.h src/lib/strfuncs.c src/lib/strfuncs.h src/pop3-login/client-authenticate.c src/pop3-login/client.c src/pop3/client.c src/pop3/client.h src/pop3/commands.c
diffstat 62 files changed, 2350 insertions(+), 2313 deletions(-) [+]
line wrap: on
line diff
--- a/src/auth/auth-client-connection.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/auth/auth-client-connection.c	Sun Aug 15 06:40:30 2004 +0300
@@ -25,22 +25,19 @@
 			     const void *data,
 			     struct auth_client_connection *conn)
 {
+	struct const_iovec iov[2];
 	ssize_t ret;
 
-	ret = o_stream_send(conn->output, reply, sizeof(*reply));
-	if ((size_t)ret == sizeof(*reply)) {
-		if (reply->data_size == 0) {
-			/* all sent */
-			auth_client_connection_unref(conn);
-			return;
-		}
+	iov[0].iov_base = reply;
+	iov[0].iov_len = sizeof(*reply);
+	iov[1].iov_base = data;
+	iov[2].iov_len = reply->data_size;
 
-		ret = o_stream_send(conn->output, data, reply->data_size);
-		if ((size_t)ret == reply->data_size) {
-			/* all sent */
-			auth_client_connection_unref(conn);
-			return;
-		}
+	ret = o_stream_sendv(conn->output, iov, 2);
+	if (ret == (ssize_t)(iov[0].iov_len + iov[1].iov_len)) {
+		/* all sent */
+		auth_client_connection_unref(conn);
+		return;
 	}
 
 	if (ret >= 0) {
@@ -188,6 +185,7 @@
 	static unsigned int connect_uid_counter = 0;
 	struct auth_client_connection *conn;
 	struct auth_client_handshake_reply handshake_reply;
+	struct const_iovec iov[2];
 
 	pool_t pool;
 
@@ -214,10 +212,12 @@
 	handshake_reply = *master->handshake_reply;
 	handshake_reply.connect_uid = conn->connect_uid;
 
-	if (o_stream_send(conn->output, &handshake_reply,
-			  sizeof(handshake_reply)) < 0 ||
-	    o_stream_send(conn->output, master->handshake_reply + 1,
-			  handshake_reply.data_size) < 0) {
+	iov[0].iov_base = &handshake_reply;
+	iov[0].iov_len = sizeof(handshake_reply);
+	iov[1].iov_base = master->handshake_reply + 1;
+	iov[2].iov_len = handshake_reply.data_size;
+
+	if (o_stream_sendv(conn->output, iov, 2) < 0) {
 		auth_client_connection_destroy(conn);
 		conn = NULL;
 	}
--- a/src/auth/auth-master-connection.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/auth/auth-master-connection.c	Sun Aug 15 06:40:30 2004 +0300
@@ -31,6 +31,7 @@
 	unsigned int tag;
 };
 
+static void master_output(void *context);
 static void auth_master_connection_close(struct auth_master_connection *conn);
 static int auth_master_connection_unref(struct auth_master_connection *conn);
 
@@ -94,23 +95,20 @@
 	ssize_t ret;
 
 	reply->tag = tag;
-	for (;;) {
-		ret = o_stream_send(conn->output, reply, reply_size);
-		if (ret < 0) {
-			/* master died, kill ourself too */
-			auth_master_connection_close(conn);
-			break;
-		}
 
-		if ((size_t)ret == reply_size)
-			break;
+	ret = o_stream_send(conn->output, reply, reply_size);
+	if (ret < 0) {
+		/* master died, kill ourself too */
+		auth_master_connection_close(conn);
+		return;
+	}
+	i_assert((size_t)ret == reply_size);
 
-		/* buffer full, we have to block */
-		i_warning("Master transmit buffer full, blocking..");
-		if (o_stream_flush(conn->output) < 0) {
-			/* transmit error, probably master died */
-			auth_master_connection_close(conn);
-			break;
+	if (o_stream_get_buffer_used_size(conn->output) >= MAX_OUTBUF_SIZE) {
+		/* buffer full, stop accepting more input */
+		if (conn->io != NULL) {
+			io_remove(conn->io);
+			conn->io = NULL;
 		}
 	}
 }
@@ -191,6 +189,23 @@
 	}
 }
 
+static void master_output(void *context)
+{
+	struct auth_master_connection *conn = context;
+	int ret;
+
+	if ((ret = o_stream_flush(conn->output)) < 0) {
+		/* transmit error, probably master died */
+		auth_master_connection_close(conn);
+		return;
+	}
+
+	if (o_stream_get_buffer_used_size(conn->output) <= MAX_OUTBUF_SIZE/2) {
+		/* allow input again */
+		conn->io = io_add(conn->fd, IO_READ, master_input, conn);
+	}
+}
+
 static void master_get_handshake_reply(struct auth_master_connection *master)
 {
 	struct mech_module_list *list;
@@ -242,7 +257,8 @@
 		io_remove(conn->io);
 
 	conn->output = o_stream_create_file(fd, default_pool,
-					    MAX_OUTBUF_SIZE, FALSE);
+					    (size_t)-1, FALSE);
+	o_stream_set_flush_callback(conn->output, master_output, conn);
 	conn->io = io_add(fd, IO_READ, master_input, conn);
 
 	conn->fd = fd;
@@ -269,12 +285,13 @@
 {
 	struct auth_master_handshake_reply reply;
 
-	/* just a note to master that we're ok. if we die before,
-	   master should shutdown itself. */
+	/* just a note to master that we're ok. if we die before, it means
+	   we're broken and a simple restart most likely won't help. */
 	if (conn->output != NULL) {
 		memset(&reply, 0, sizeof(reply));
 		reply.server_pid = conn->pid;
-		o_stream_send(conn->output, &reply, sizeof(reply));
+		if (o_stream_send(conn->output, &reply, sizeof(reply)) < 0)
+                        auth_master_connection_close(conn);
 	}
 }
 
@@ -290,8 +307,10 @@
 	o_stream_close(conn->output);
 	conn->output = NULL;
 
-	io_remove(conn->io);
-	conn->io = NULL;
+	if (conn->io != NULL) {
+		io_remove(conn->io);
+		conn->io = NULL;
+	}
 }
 
 void auth_master_connection_destroy(struct auth_master_connection *conn)
--- a/src/auth/password-scheme.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/auth/password-scheme.c	Sun Aug 15 06:40:30 2004 +0300
@@ -64,7 +64,7 @@
 			/* stop at next '$' */
 			p = strchr(p+1, '$');
 			if (p != NULL)
-				*password = t_strdup_until(*password, p);
+				*password = t_strdup_until(*password + 3, p);
 			return "MD5";
 		}
 	}
--- a/src/imap-login/client-authenticate.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap-login/client-authenticate.c	Sun Aug 15 06:40:30 2004 +0300
@@ -53,7 +53,6 @@
 	client_send_tagline(client, msg != NULL ?
 			    t_strconcat("NO ", msg, NULL) :
 			    "NO Authentication failed.");
-	o_stream_flush(client->output);
 
 	/* get back to normal client input */
 	if (client->common.io != NULL)
@@ -86,6 +85,9 @@
 				  const unsigned char *data, size_t size)
 {
 	buffer_t *buf;
+	const void *buf_data;
+	size_t buf_size;
+	ssize_t ret;
 
 	t_push();
 
@@ -95,9 +97,11 @@
 	base64_encode(data, size, buf);
 	buffer_append(buf, "\r\n", 2);
 
-	o_stream_send(client->output, buffer_get_data(buf, NULL),
-		      buffer_get_used_size(buf));
-	o_stream_flush(client->output);
+	buf_data = buffer_get_data(buf, &buf_size);
+	if ((ret = o_stream_send(client->output, buf_data, buf_size) < 0))
+		client_destroy(client, "Disconnected");
+	else if ((size_t)ret != buf_size)
+		client_destroy(client, "Transmit buffer full");
 
 	t_pop();
 }
@@ -315,6 +319,8 @@
 	info.remote_ip = client->common.ip;
 
 	client_ref(client);
+	o_stream_uncork(client->output);
+
 	client->common.auth_request =
 		auth_client_request_new(auth_client, NULL, &info,
 					authenticate_callback, client, &error);
--- a/src/imap-login/client.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap-login/client.c	Sun Aug 15 06:40:30 2004 +0300
@@ -15,10 +15,13 @@
 #include "auth-client.h"
 #include "ssl-proxy.h"
 
-/* max. size of one parameter in line */
-#define MAX_INBUF_SIZE 512
+/* max. size of one parameter in line, or max reply length in SASL
+   authentication */
+#define MAX_INBUF_SIZE 4096
 
-#define MAX_OUTBUF_SIZE 1024
+/* max. size of output buffer. if it gets full, the client is disconnected.
+   SASL authentication gives the largest output. */
+#define MAX_OUTBUF_SIZE 4096
 
 /* maximum length for IMAP command line. */
 #define MAX_IMAP_LINE 8192
@@ -100,10 +103,51 @@
 	return TRUE;
 }
 
-static int cmd_starttls(struct imap_client *client)
+static void client_start_tls(struct imap_client *client)
 {
 	int fd_ssl;
 
+	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
+			       &client->common.proxy);
+	if (fd_ssl == -1) {
+		client_send_line(client, "* BYE TLS initialization failed.");
+		client_destroy(client, "TLS initialization failed.");
+		return;
+	}
+
+	client->tls = TRUE;
+	client->secured = TRUE;
+	client_set_title(client);
+
+	client->common.fd = fd_ssl;
+	i_stream_unref(client->input);
+	o_stream_unref(client->output);
+	imap_parser_destroy(client->parser);
+
+	/* CRLF is lost from buffer when streams are reopened. */
+	client->skip_line = FALSE;
+
+	client_open_streams(client, fd_ssl);
+	client->common.io = io_add(client->common.fd, IO_READ,
+				   client_input, client);
+}
+
+static void client_output_starttls(void *context)
+{
+	struct imap_client *client = context;
+	int ret;
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client, "Disconnected");
+		return;
+	}
+
+	if (ret > 0)
+		client_start_tls(client);
+}
+
+static int cmd_starttls(struct imap_client *client)
+{
 	if (client->tls) {
 		client_send_tagline(client, "BAD TLS is already active.");
 		return TRUE;
@@ -114,40 +158,21 @@
 		return TRUE;
 	}
 
-	client_send_tagline(client, "OK Begin TLS negotiation now.");
-	o_stream_flush(client->output);
-
-	/* must be removed before ssl_proxy_new(), since it may
-	   io_add() the same fd. */
+	/* remove input handler, SSL proxy gives us a new fd. we also have to
+	   remove it in case we have to wait for buffer to be flushed */
 	if (client->common.io != NULL) {
 		io_remove(client->common.io);
 		client->common.io = NULL;
 	}
 
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
-			       &client->common.proxy);
-	if (fd_ssl != -1) {
-		client->tls = TRUE;
-		client->secured = TRUE;
-                client_set_title(client);
-
-		/* we skipped it already, so don't ignore next command */
-		client->skip_line = FALSE;
-
-		client->common.fd = fd_ssl;
-
-		i_stream_unref(client->input);
-		o_stream_unref(client->output);
-		imap_parser_destroy(client->parser);
-
-		client_open_streams(client, fd_ssl);
-		client->common.io = io_add(client->common.fd, IO_READ,
-					   client_input, client);
+	client_send_tagline(client, "OK Begin TLS negotiation now.");
+	if (o_stream_get_buffer_used_size(client->output) != 0) {
+		/* the buffer has to be flushed */
+		o_stream_set_flush_callback(client->output,
+					    client_output_starttls, client);
 	} else {
-		client_send_line(client, "* BYE TLS initialization failed.");
-		client_destroy(client, "TLS initialization failed.");
+		client_start_tls(client);
 	}
-
 	return TRUE;
 }
 
@@ -292,22 +317,21 @@
 	if (!client_read(client))
 		return;
 
+	client_ref(client);
+
 	if (!auth_client_is_connected(auth_client)) {
 		/* we're not yet connected to auth process -
 		   don't allow any commands */
 		client_send_line(client,
 			"* OK Waiting for authentication process to respond..");
 		client->input_blocked = TRUE;
-		return;
+	} else {
+		o_stream_cork(client->output);
+		while (client_handle_input(client)) ;
+		o_stream_uncork(client->output);
 	}
 
-	client_ref(client);
-
-	o_stream_cork(client->output);
-	while (client_handle_input(client)) ;
-
-	if (client_unref(client))
-		o_stream_flush(client->output);
+	client_unref(client);
 }
 
 static void client_destroy_oldest(void)
@@ -452,8 +476,18 @@
 
 void client_send_line(struct imap_client *client, const char *line)
 {
-	o_stream_send_str(client->output, line);
-	o_stream_send(client->output, "\r\n", 2);
+	struct const_iovec iov[2];
+	ssize_t ret;
+
+	iov[0].iov_base = line;
+	iov[0].iov_len = strlen(line);
+	iov[1].iov_base = "\r\n";
+	iov[1].iov_len = 2;
+
+	if ((ret = o_stream_sendv(client->output, iov, 2)) < 0)
+		client_destroy(client, "Disconnected");
+	else if ((size_t)ret != iov[0].iov_len + iov[1].iov_len)
+		client_destroy(client, "Transmit buffer full");
 }
 
 void client_send_tagline(struct imap_client *client, const char *line)
--- a/src/imap/Makefile.am	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/Makefile.am	Sun Aug 15 06:40:30 2004 +0300
@@ -64,7 +64,7 @@
 	commands-util.c \
 	imap-expunge.c \
 	imap-fetch.c \
-	imap-fetch-body-section.c \
+	imap-fetch-body.c \
 	imap-messageset.c \
 	imap-search.c \
 	imap-sort.c \
--- a/src/imap/client.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/client.c	Sun Aug 15 06:40:30 2004 +0300
@@ -1,4 +1,4 @@
-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002-2004 Timo Sirainen */
 
 #include "common.h"
 #include "ioloop.h"
@@ -10,60 +10,36 @@
 
 #include <stdlib.h>
 
-/* If we can't send a buffer in a minute, disconnect the client */
-#define CLIENT_OUTPUT_TIMEOUT (60*1000)
-
-/* If we don't soon receive expected data from client while processing
-   a command, disconnect the client */
-#define CLIENT_CMDINPUT_TIMEOUT CLIENT_OUTPUT_TIMEOUT
-
-/* Disconnect client when it sends too many bad commands in a row */
-#define CLIENT_MAX_BAD_COMMANDS 20
-
 extern struct mail_storage_callbacks mail_storage_callbacks;
 
 static struct client *my_client; /* we don't need more than one currently */
 static struct timeout *to_idle;
 
-static void client_output_timeout(void *context)
-{
-	struct client *client = context;
-
-	i_stream_close(client->input);
-	o_stream_close(client->output);
-}
-
-static void client_input_timeout(void *context)
-{
-	struct client *client = context;
-
-	client_disconnect_with_error(client,
-		"Disconnected for inactivity while waiting for command data.");
-}
+static void client_input(void *context);
+static void client_output(void *context);
 
 struct client *client_create(int hin, int hout, struct namespace *namespaces)
 {
 	struct client *client;
 
+	/* always use nonblocking I/O */
+	net_set_nonblock(hin, TRUE);
+	net_set_nonblock(hout, TRUE);
+
 	client = i_new(struct client, 1);
 	client->input = i_stream_create_file(hin, default_pool,
 					     imap_max_line_length, FALSE);
-	client->output = o_stream_create_file(hout, default_pool, 4096, FALSE);
+	client->output = o_stream_create_file(hout, default_pool,
+					      (size_t)-1, FALSE);
 
-	/* set timeout for reading expected data (eg. APPEND). This is
-	   different from the actual idle time. */
-	i_stream_set_blocking(client->input, CLIENT_CMDINPUT_TIMEOUT,
-			      client_input_timeout, client);
+	o_stream_set_flush_callback(client->output, client_output, client);
 
-	/* set timeout for sending data */
-	o_stream_set_blocking(client->output, CLIENT_OUTPUT_TIMEOUT,
-			      client_output_timeout, client);
-
-	client->io = io_add(hin, IO_READ, _client_input, client);
+	client->io = io_add(hin, IO_READ, client_input, client);
 	client->parser = imap_parser_create(client->input, client->output,
 					    imap_max_line_length);
         client->last_input = ioloop_time;
 
+	client->cmd_pool = pool_alloconly_create("command pool", 8192);
 	client->keywords.pool = pool_alloconly_create("mailbox_keywords", 512);
 	client->namespaces = namespaces;
 
@@ -83,14 +59,13 @@
 
 void client_destroy(struct client *client)
 {
-	o_stream_flush(client->output);
-
 	if (client->mailbox != NULL)
 		mailbox_close(client->mailbox);
 	namespace_deinit(client->namespaces);
 
 	imap_parser_destroy(client->parser);
-	io_remove(client->io);
+	if (client->io != NULL)
+		io_remove(client->io);
 
 	if (client->idle_to != NULL)
 		timeout_remove(client->idle_to);
@@ -99,6 +74,7 @@
 	o_stream_unref(client->output);
 
 	pool_unref(client->keywords.pool);
+	pool_unref(client->cmd_pool);
 	i_free(client);
 
 	/* quit the program */
@@ -108,7 +84,7 @@
 
 void client_disconnect(struct client *client)
 {
-	o_stream_flush(client->output);
+	(void)o_stream_flush(client->output);
 
 	i_stream_close(client->input);
 	o_stream_close(client->output);
@@ -168,7 +144,6 @@
 				    cmd, ": ", msg, NULL);
 	}
 
-	client->cmd_error = TRUE;
 	client_send_tagline(client, error);
 
 	if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
@@ -234,12 +209,22 @@
 
 void _client_reset_command(struct client *client)
 {
+	/* reset input idle time because command output might have taken a
+	   long time and we don't want to disconnect client immediately then */
+	client->last_input = ioloop_time;
+
+	client->command_pending = FALSE;
+	if (client->io == NULL) {
+		client->io = io_add(i_stream_get_fd(client->input),
+				    IO_READ, client_input, client);
+	}
+
 	client->cmd_tag = NULL;
 	client->cmd_name = NULL;
 	client->cmd_func = NULL;
-	client->cmd_error = FALSE;
 	client->cmd_uid = FALSE;
 
+	p_clear(client->cmd_pool);
         imap_parser_reset(client->parser);
 }
 
@@ -268,8 +253,7 @@
 {
         if (client->cmd_func != NULL) {
 		/* command is being executed - continue it */
-		client->input_skip_line = TRUE;
-		if (client->cmd_func(client) || client->cmd_error) {
+		if (client->cmd_func(client)) {
 			/* command execution was finished */
 			_client_reset_command(client);
                         client->bad_counter = 0;
@@ -293,12 +277,14 @@
                 client->cmd_tag = imap_parser_read_word(client->parser);
 		if (client->cmd_tag == NULL)
 			return FALSE; /* need more data */
+		client->cmd_tag = p_strdup(client->cmd_pool, client->cmd_tag);
 	}
 
 	if (client->cmd_name == NULL) {
-                client->cmd_name = imap_parser_read_word(client->parser);
+		client->cmd_name = imap_parser_read_word(client->parser);
 		if (client->cmd_name == NULL)
 			return FALSE; /* need more data */
+		client->cmd_name = p_strdup(client->cmd_pool, client->cmd_name);
 	}
 
 	if (client->cmd_name == '\0') {
@@ -315,7 +301,7 @@
 		_client_reset_command(client);
 	} else {
 		client->input_skip_line = TRUE;
-		if (client->cmd_func(client) || client->cmd_error) {
+		if (client->cmd_func(client)) {
 			/* command execution was finished */
 			_client_reset_command(client);
                         client->bad_counter = 0;
@@ -328,10 +314,16 @@
 	return TRUE;
 }
 
-void _client_input(void *context)
+static void client_input(void *context)
 {
 	struct client *client = context;
 
+	if (client->command_pending) {
+		/* already processing one command. wait. */
+		io_remove(client->io);
+		client->io = NULL;
+	}
+
 	client->last_input = ioloop_time;
 
 	switch (i_stream_read(client->input)) {
@@ -353,20 +345,56 @@
 	o_stream_cork(client->output);
 	while (client_handle_input(client))
 		;
-	o_stream_flush(client->output);
+	o_stream_uncork(client->output);
 
 	if (client->output->closed)
 		client_destroy(client);
 }
 
+static void client_output(void *context)
+{
+	struct client *client = context;
+	int ret;
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client);
+		return;
+	}
+
+	client->last_output = ioloop_time;
+
+	if (client->command_pending) {
+		o_stream_cork(client->output);
+		if (client->cmd_func(client)) {
+			/* command execution was finished */
+			_client_reset_command(client);
+                        client->bad_counter = 0;
+		}
+		o_stream_uncork(client->output);
+	}
+}
+
 static void idle_timeout(void *context __attr_unused__)
 {
+	time_t idle_time;
+
 	if (my_client == NULL)
 		return;
 
-	if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) {
-		client_send_line(my_client,
-				 "* BYE Disconnected for inactivity.");
+	idle_time = ioloop_time -
+		I_MAX(my_client->last_input, my_client->last_output);
+
+	if (my_client->command_pending &&
+	    o_stream_get_buffer_used_size(my_client->output) > 0 &&
+	    idle_time >= CLIENT_OUTPUT_TIMEOUT) {
+		/* client isn't reading our output */
+		client_destroy(my_client);
+	} else if (idle_time >= CLIENT_IDLE_TIMEOUT) {
+		/* client isn't sending us anything */
+		if (!my_client->command_pending) {
+			client_send_line(my_client,
+					 "* BYE Disconnected for inactivity.");
+		}
 		client_destroy(my_client);
 	}
 }
--- a/src/imap/client.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/client.h	Sun Aug 15 06:40:30 2004 +0300
@@ -27,18 +27,20 @@
 	unsigned int select_counter; /* increased when mailbox is changed */
 	uint32_t messages_count, recent_count;
 
-	time_t last_input;
+	time_t last_input, last_output;
 	unsigned int bad_counter;
 
 	struct imap_parser *parser;
-	const char *cmd_tag; /* tag of command (allocated from parser pool), */
-	const char *cmd_name; /* command name (allocated from parser pool) */
+	pool_t cmd_pool;
+	const char *cmd_tag;
+	const char *cmd_name;
 	command_func_t *cmd_func;
+	void *cmd_context;
 
 	struct timeout *idle_to;
 	unsigned int idle_expunge;
 
-	unsigned int cmd_error:1;
+	unsigned int command_pending:1;
 	unsigned int cmd_uid:1; /* used UID command */
 	unsigned int rawlog:1;
 	unsigned int input_skip_line:1; /* skip all the data until we've
@@ -73,7 +75,6 @@
 void clients_init(void);
 void clients_deinit(void);
 
-void _client_input(void *context);
 void _client_reset_command(struct client *client);
 
 #endif
--- a/src/imap/cmd-append.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/cmd-append.c	Sun Aug 15 06:40:30 2004 +0300
@@ -121,19 +121,16 @@
 
 			/* need more data */
 			ret = i_stream_read(client->input);
-			if (ret == -2) {
-				client_send_command_error(client,
-							  "Too long argument.");
-				break;
-			}
 			if (ret < 0) {
-				/* disconnected */
-				client->cmd_error = TRUE;
+				if (ret == -2) {
+					client_send_command_error(client,
+						"Too long argument.");
+				}
 				break;
 			}
 		}
 
-		if (client->cmd_error)
+		if (ret < 0)
 			break;
 
 		if (args->type == IMAP_ARG_EOL) {
--- a/src/imap/cmd-fetch.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/cmd-fetch.c	Sun Aug 15 06:40:30 2004 +0300
@@ -1,4 +1,4 @@
-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002-2004 Timo Sirainen */
 
 #include "common.h"
 #include "commands.h"
@@ -6,360 +6,64 @@
 #include "imap-search.h"
 #include "mail-search.h"
 
-/* Parse next digits in string into integer. Returns FALSE if the integer
-   becomes too big and wraps. */
-static int read_uoff_t(char **p, uoff_t *value)
+static int
+fetch_parse_args(struct client *client, struct imap_fetch_context *ctx,
+		 struct imap_arg *arg)
 {
-	uoff_t prev;
+	const char *str;
+
+	if (arg->type == IMAP_ARG_ATOM) {
+		str = str_ucase(IMAP_ARG_STR(arg));
 
-	*value = 0;
-	while (**p >= '0' && **p <= '9') {
-		prev = *value;
-		*value = *value * 10 + (**p - '0');
+		/* handle macros first */
+		if (strcmp(str, "ALL") == 0) {
+			if (!imap_fetch_init_handler(ctx, "FLAGS") ||
+			    !imap_fetch_init_handler(ctx, "INTERNALDATE") ||
+			    !imap_fetch_init_handler(ctx, "RFC822.SIZE") ||
+			    !imap_fetch_init_handler(ctx, "ENVELOPE"))
+				return FALSE;
+		} else if (strcmp(str, "FAST") == 0) {
+			if (!imap_fetch_init_handler(ctx, "FLAGS") ||
+			    !imap_fetch_init_handler(ctx, "INTERNALDATE") ||
+			    !imap_fetch_init_handler(ctx, "RFC822.SIZE"))
+				return FALSE;
+		} else if (strcmp(str, "FULL") == 0) {
+			if (!imap_fetch_init_handler(ctx, "FLAGS") ||
+			    !imap_fetch_init_handler(ctx, "INTERNALDATE") ||
+			    !imap_fetch_init_handler(ctx, "RFC822.SIZE") ||
+			    !imap_fetch_init_handler(ctx, "ENVELOPE") ||
+			    !imap_fetch_init_handler(ctx, "BODY"))
+				return FALSE;
+		} else {
+			if (!imap_fetch_init_handler(ctx, str))
+				return FALSE;
+		}
+	} else {
+		arg = IMAP_ARG_LIST(arg)->args;
+		while (arg->type == IMAP_ARG_ATOM) {
+			str = str_ucase(IMAP_ARG_STR(arg));
+			if (!imap_fetch_init_handler(ctx, str))
+				return FALSE;
+			arg++;
+		}
+		if (arg->type != IMAP_ARG_EOL) {
+			client_send_command_error(client,
+				"FETCH list contains non-atoms.");
+			return FALSE;
+		}
+	}
 
-		if (*value < prev)
+	if (client->cmd_uid) {
+		if (!imap_fetch_init_handler(ctx, "UID"))
 			return FALSE;
-
-		(*p)++;
 	}
 
 	return TRUE;
 }
 
-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)
-{
-	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) {
-		/* exact header matches could be cached */
-		if (strncmp(section, "HEADER.FIELDS ", 14) != 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;
-	char *p;
-
-	body = t_new(struct imap_fetch_body_data, 1);
-	body->peek = peek;
-
-	p = t_strdup_noconst(item);
-
-	/* read section */
-	body->section = p;
-	for (; *p != ']'; p++) {
-		if (*p == '\0') {
-			client_send_tagline(client, t_strconcat(
-				"BAD Missing ']' with ", item, NULL));
-			return FALSE;
-		}
-	}
-	*p++ = '\0';
-
-	if (!check_section(client, body->section, fetch_data))
-		return FALSE;
-
-	/* <start.end> */
-	body->skip = 0;
-	body->max_size = (uoff_t)-1;
-	if (*p != '<' && *p != '\0') {
-		client_send_tagline(client, t_strconcat(
-			"BAD Unexpected character after ']' with ",
-			item, NULL));
-	} else if (*p == '<') {
-		/* read start */
-		p++;
-
-		body->skip_set = TRUE;
-		if (!read_uoff_t(&p, &num) || num > OFF_T_MAX) {
-			/* wrapped */
-			client_send_tagline(client, t_strconcat(
-				"BAD Too big partial start with ", item, NULL));
-			return FALSE;
-		}
-		body->skip = num;
-
-		if (*p == '.') {
-			/* read end */
-			p++;
-			if (!read_uoff_t(&p, &num) || num > OFF_T_MAX) {
-				/* wrapped */
-				client_send_tagline(client, t_strconcat(
-					"BAD Too big partial end with ",
-					item, NULL));
-				return FALSE;
-			}
-
-                        body->max_size = num;
-		}
-
-		if (*p != '>') {
-			client_send_tagline(client, t_strconcat(
-				"BAD Invalid partial ", item, NULL));
-			return FALSE;
-		}
-	}
-
-	**bodies = body;
-	*bodies = &body->next;
-	return TRUE;
-}
-
-static int parse_arg(struct client *client, struct imap_arg *arg,
-		     enum mail_fetch_field *fetch_data,
-		     enum imap_fetch_field *imap_data,
-		     struct imap_fetch_body_data ***bodies)
+static void cmd_fetch_finish(struct client *client, int failed)
 {
-	char *item;
-
-	if (arg->type != IMAP_ARG_ATOM) {
-		client_send_command_error(client,
-					  "FETCH list contains non-atoms.");
-		return FALSE;
-	}
-
-	item = str_ucase(IMAP_ARG_STR(arg));
-
-	switch (*item) {
-	case 'A':
-		if (strcmp(item, "ALL") == 0) {
-			*fetch_data |= MAIL_FETCH_FLAGS |
-				MAIL_FETCH_RECEIVED_DATE |
-				MAIL_FETCH_SIZE |
-				MAIL_FETCH_IMAP_ENVELOPE;
-		} else
-			item = NULL;
-		break;
-	case 'B':
-		/* all start with BODY so skip it */
-		if (strncmp(item, "BODY", 4) != 0) {
-			item = NULL;
-			break;
-		}
-		item += 4;
-
-		if (*item == '\0') {
-			/* BODY */
-			*fetch_data |= MAIL_FETCH_IMAP_BODY;
-		} else if (*item == '[') {
-			/* BODY[...] */
-			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, TRUE,
-						fetch_data, bodies))
-				return FALSE;
-		} else if (strcmp(item, "STRUCTURE") == 0) {
-			/* BODYSTRUCTURE */
-			*fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
-		} else
-			item = NULL;
-		break;
-	case 'E':
-		if (strcmp(item, "ENVELOPE") == 0)
-			*fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
-		else
-			item = NULL;
-		break;
-	case 'F':
-		if (strcmp(item, "FLAGS") == 0)
-			*fetch_data |= MAIL_FETCH_FLAGS;
-		else if (strcmp(item, "FAST") == 0) {
-			*fetch_data |= MAIL_FETCH_FLAGS |
-				MAIL_FETCH_RECEIVED_DATE |
-				MAIL_FETCH_SIZE;
-		} else if (strcmp(item, "FULL") == 0) {
-			*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)
-			*fetch_data |= MAIL_FETCH_RECEIVED_DATE;
-		else
-			item = NULL;
-		break;
-	case 'R':
-		/* all start with RFC822 so skip it */
-		if (strncmp(item, "RFC822", 6) != 0) {
-			item = NULL;
-			break;
-		}
-		item += 6;
-
-		if (*item == '\0') {
-			/* RFC822 */
-			*fetch_data |= MAIL_FETCH_STREAM_HEADER |
-				MAIL_FETCH_STREAM_BODY;
-			*imap_data |= IMAP_FETCH_RFC822;
-			break;
-		}
-
-		/* only items beginning with "RFC822." left */
-		if (*item != '.') {
-			item = NULL;
-			break;
-		}
-		item++;
-
-		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)
-			*imap_data |= IMAP_FETCH_UID;
-		else
-			item = NULL;
-		break;
-	default:
-		item = NULL;
-		break;
-	}
-
-	if (item == NULL) {
-		/* unknown item */
-		client_send_tagline(client, t_strconcat(
-			"BAD Invalid item ", IMAP_ARG_STR(arg), NULL));
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-int cmd_fetch(struct client *client)
-{
-	struct imap_arg *args, *listargs;
-	enum mail_fetch_field fetch_data;
-	enum imap_fetch_field imap_data;
-	struct imap_fetch_body_data *bodies, **bodies_p;
-	struct mail_search_arg *search_arg;
-	const char *messageset;
-	int ret;
-
-	if (!client_read_args(client, 2, 0, &args))
-		return FALSE;
-
-	if (!client_verify_open_mailbox(client))
-		return TRUE;
-
-	messageset = imap_arg_string(&args[0]);
-	if (messageset == NULL ||
-	    (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM)) {
-		client_send_command_error(client, "Invalid arguments.");
-		return TRUE;
-	}
-
-	/* parse items argument */
-	fetch_data = 0; imap_data = 0; bodies = NULL; bodies_p = &bodies;
-	if (args[1].type == IMAP_ARG_ATOM) {
-		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, &fetch_data,
-				       &imap_data, &bodies_p))
-				return TRUE;
-
-			listargs++;
-		}
-	}
-
-	if (client->cmd_uid)
-		imap_data |= IMAP_FETCH_UID;
-
-	search_arg = imap_search_get_arg(client, messageset, client->cmd_uid);
-	if (search_arg == NULL)
-		return TRUE;
-
-	ret = imap_fetch(client, fetch_data, imap_data, bodies, search_arg);
-	if (ret == 0) {
+	if (!failed) {
 		if ((client_workarounds &
 		     WORKAROUND_OE6_FETCH_NO_NEWMAIL) == 0) {
 			if (client->cmd_uid)
@@ -389,6 +93,67 @@
 			client_send_storage_error(client, storage);
 		}
 	}
+}
 
+static int cmd_fetch_continue(struct client *client)
+{
+        struct imap_fetch_context *ctx = client->cmd_context;
+	int ret;
+
+	if ((ret = imap_fetch(ctx)) == 0) {
+		/* unfinished */
+		return FALSE;
+	}
+	if (imap_fetch_deinit(ctx) < 0)
+		ret = -1;
+	cmd_fetch_finish(client, ret < 0);
 	return TRUE;
 }
+
+int cmd_fetch(struct client *client)
+{
+        struct imap_fetch_context *ctx;
+	struct imap_arg *args;
+	struct mail_search_arg *search_arg;
+	const char *messageset;
+	int ret;
+
+	if (!client_read_args(client, 2, 0, &args))
+		return FALSE;
+
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	messageset = imap_arg_string(&args[0]);
+	if (messageset == NULL ||
+	    (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM)) {
+		client_send_command_error(client, "Invalid arguments.");
+		return TRUE;
+	}
+
+	search_arg = imap_search_get_arg(client, messageset, client->cmd_uid);
+	if (search_arg == NULL)
+		return TRUE;
+
+	ctx = imap_fetch_init(client);
+	if (ctx == NULL)
+		return TRUE;
+
+	if (!fetch_parse_args(client, ctx, &args[1])) {
+		imap_fetch_deinit(ctx);
+		return TRUE;
+	}
+
+	imap_fetch_begin(ctx, search_arg);
+	if ((ret = imap_fetch(ctx)) == 0) {
+		/* unfinished */
+		client->command_pending = TRUE;
+		client->cmd_func = cmd_fetch_continue;
+		client->cmd_context = ctx;
+		return FALSE;
+	}
+	if (imap_fetch_deinit(ctx) < 0)
+		ret = -1;
+	cmd_fetch_finish(client, ret < 0);
+	return TRUE;
+}
--- a/src/imap/cmd-idle.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/cmd-idle.c	Sun Aug 15 06:40:30 2004 +0300
@@ -26,8 +26,7 @@
 	}
 
 	io_remove(client->io);
-	client->io = io_add(i_stream_get_fd(client->input),
-			    IO_READ, _client_input, client);
+	client->io = NULL;
 
 	if (client->mailbox != NULL)
 		mailbox_notify_changes(client->mailbox, 0, NULL, NULL);
@@ -38,7 +37,7 @@
 	else
 		client_send_tagline(client, "BAD Expected DONE.");
 
-	o_stream_flush(client->output);
+	o_stream_uncork(client->output);
 
 	_client_reset_command(client);
 	client->bad_counter = 0;
--- a/src/imap/cmd-search.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/cmd-search.c	Sun Aug 15 06:40:30 2004 +0300
@@ -58,7 +58,6 @@
 	struct mail_search_arg *sargs;
 	struct imap_arg *args;
 	int args_count;
-	pool_t pool;
 	const char *error, *charset;
 
 	args_count = imap_parser_read_args(client->parser, 0, 0, &args);
@@ -91,9 +90,7 @@
 		charset = NULL;
 	}
 
-	pool = pool_alloconly_create("mail_search_args", 2048);
-
-	sargs = imap_search_args_build(pool, client->mailbox, args, &error);
+	sargs = imap_search_args_build(client->cmd_pool, client->mailbox, args, &error);
 	if (sargs == NULL) {
 		/* error in search arguments */
 		client_send_tagline(client, t_strconcat("NO ", error, NULL));
@@ -108,6 +105,5 @@
 					  mailbox_get_storage(client->mailbox));
 	}
 
-	pool_unref(pool);
 	return TRUE;
 }
--- a/src/imap/common.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/common.h	Sun Aug 15 06:40:30 2004 +0300
@@ -7,6 +7,15 @@
 /* Disconnect client after idling this many seconds */
 #define CLIENT_IDLE_TIMEOUT (60*30)
 
+/* If we can't send anything to client for this long, disconnect the client */
+#define CLIENT_OUTPUT_TIMEOUT (5*60)
+
+/* Stop buffering more data into output stream after this many bytes */
+#define CLIENT_OUTPUT_OPTIMAL_SIZE 2048
+
+/* Disconnect client when it sends too many bad commands in a row */
+#define CLIENT_MAX_BAD_COMMANDS 20
+
 /* RFC-2683 recommends at least 8000 bytes. Some clients however don't
    break large message sets to multiple commands, so we're pretty liberal
    by default. */
--- a/src/imap/imap-fetch-body-section.c	Sun Aug 15 05:54:47 2004 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,554 +0,0 @@
-/* Copyright (C) 2002 Timo Sirainen */
-
-#include "common.h"
-#include "buffer.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)
-
-struct fetch_header_field_context {
-        struct imap_fetch_context *fetch_ctx;
-	struct mail *mail;
-
-	buffer_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 char *, size_t);
-
-	unsigned int fix_nuls:1;
-};
-
-struct partial_cache {
-	unsigned int select_counter;
-	unsigned int uid;
-
-	uoff_t physical_start;
-	int cr_skipped;
-	struct message_size pos;
-};
-
-static struct partial_cache partial = { 0, 0, 0, 0, { 0, 0, 0 } };
-
-static int seek_partial(unsigned int select_counter, unsigned int uid,
-			struct partial_cache *partial, struct istream *stream,
-			uoff_t physical_start, uoff_t virtual_skip)
-{
-	int cr_skipped;
-
-	if (select_counter == partial->select_counter && uid == partial->uid &&
-	    physical_start == partial->physical_start &&
-	    virtual_skip >= partial->pos.virtual_size) {
-		/* we can use the cache */
-		virtual_skip -= partial->pos.virtual_size;
-	} else {
-		partial->select_counter = select_counter;
-		partial->uid = uid;
-		partial->physical_start = physical_start;
-		partial->cr_skipped = FALSE;
-		memset(&partial->pos, 0, sizeof(partial->pos));
-	}
-
-	i_stream_seek(stream, partial->physical_start +
-		      partial->pos.physical_size);
-	message_skip_virtual(stream, virtual_skip, &partial->pos,
-			     partial->cr_skipped, &cr_skipped);
-
-	partial->cr_skipped = FALSE;
-	return cr_skipped;
-}
-
-static uoff_t get_send_size(const struct imap_fetch_body_data *body,
-			    uoff_t max_size)
-{
-	uoff_t size;
-
-	if (body->skip >= max_size)
-		return 0;
-
-	size = max_size - body->skip;
-	return size <= body->max_size ? size : body->max_size;
-}
-
-static int fetch_data(struct imap_fetch_context *ctx,
-		      const struct imap_fetch_body_data *body,
-		      struct mail *mail, struct istream *input,
-		      uoff_t physical_start, const struct message_size *size)
-{
-	const char *str;
-	uoff_t send_size;
-	off_t ret;
-	int skip_cr, last_cr;
-
-	send_size = get_send_size(body, size->virtual_size);
-
-	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", ctx->prefix, send_size);
-	if (o_stream_send_str(ctx->output, str) < 0)
-		return FALSE;
-
-	skip_cr = seek_partial(ctx->select_counter, mail->uid,
-			       &partial, input, physical_start, body->skip);
-
-	ret = message_send(ctx->output, input, size, skip_cr, send_size,
-			   &last_cr, !mail->has_no_nuls);
-	if (ret > 0) {
-		partial.cr_skipped = last_cr != 0;
-		partial.pos.physical_size =
-			input->v_offset - partial.physical_start;
-		partial.pos.virtual_size += ret;
-	}
-
-	if (ret != (off_t)send_size) {
-		/* Input stream gave less data then we expected. Two choices
-		   here: either we fill the missing data with spaces or we
-		   disconnect the client.
-
-		   We shouldn't really ever get here. One reason is if mail
-		   was deleted from NFS server while we were reading it.
-		   Another is some temporary disk error.
-
-		   If we filled the missing data the client could cache it,
-		   and if it was just a temporary error the message would be
-		   permanently left corrupted in client's local cache. So, we
-		   disconnect the client and hope that next try works. */
-		o_stream_close(ctx->output);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-/* 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;
-
-	stream = mail->get_stream(mail, &hdr_size, &body_size);
-	if (stream == NULL)
-		return FALSE;
-
-	if (fetch_header)
-		message_size_add(&body_size, &hdr_size);
-
-	return fetch_data(ctx, body, mail, stream,
-			  fetch_header ? 0 : hdr_size.physical_size,
-			  &body_size);
-}
-
-static int header_match(const char *const *fields,
-			const char *name, size_t size)
-{
-	const char *name_start, *name_end, *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 char *name, size_t size)
-{
-	return !header_match(fields, name, size);
-}
-
-static int header_match_mime(const char *const *fields __attr_unused__,
-			     const char *name, size_t size)
-{
-	if (strncasecmp(name, "Content-", 8) == 0)
-		return TRUE;
-
-	if (size == 12 && strcasecmp(name, "Mime-Version") == 0)
-		return TRUE;
-
-	return FALSE;
-}
-
-static int fetch_header_append(struct fetch_header_field_context *ctx,
-			       const void *data, size_t size)
-{
-	const unsigned char *str = data;
-	size_t i;
-
-	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;
-	}
-
-	ctx->dest_size += size;
-
-	if (ctx->fix_nuls && (ctx->dest != NULL || ctx->output != NULL)) {
-		for (i = 0; i < size; ) {
-			if (str[i] != 0) {
-				i++;
-				continue;
-			}
-
-			/* NUL found, change it to #128 */
-			if (ctx->dest != NULL) {
-				buffer_append(ctx->dest, str, i);
-				buffer_append(ctx->dest, "\x80", 1);
-			} else {
-				if (o_stream_send(ctx->output, str, i) < 0 ||
-				    o_stream_send(ctx->output, "\x80", 1) < 0)
-					return FALSE;
-			}
-
-			str += i+1;
-			size -= i+1;
-			i = 0;
-		}
-	}
-
-	if (ctx->dest != NULL)
-		buffer_append(ctx->dest, str, size);
-	else {
-		if (o_stream_send(ctx->output, str, size) < 0)
-			return FALSE;
-	}
-
-	return ctx->dest_size < ctx->max_size;
-}
-
-static int fetch_header_fields(struct istream *input, const char *section,
-			       struct fetch_header_field_context *ctx)
-{
-	struct message_header_parser_ctx *hdr_ctx;
-	struct message_header_line *hdr;
-
-	if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
-		ctx->fields = imap_fetch_get_body_fields(section + 14);
-		ctx->match_func = header_match;
-
-		if (ctx->fetch_ctx->body_fetch_from_cache) {
-			input = ctx->mail->
-				get_headers(ctx->mail,
-					    ctx->fetch_ctx->headers_ctx);
-			if (input == NULL)
-				return FALSE;
-		}
-	} else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-		ctx->fields = imap_fetch_get_body_fields(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;
-
-	hdr_ctx = message_parse_header_init(input, NULL, FALSE);
-	while ((hdr = message_parse_header_next(hdr_ctx)) != NULL) {
-		/* see if we want this field. */
-		if (!ctx->match_func(ctx->fields, hdr->name, hdr->name_len))
-			continue;
-
-		if (!hdr->continued) {
-			if (!fetch_header_append(ctx, hdr->name, hdr->name_len))
-				break;
-			if (!fetch_header_append(ctx, hdr->middle,
-						 hdr->middle_len))
-				break;
-		}
-		if (!fetch_header_append(ctx, hdr->value, hdr->value_len))
-			break;
-		if (!hdr->no_newline) {
-			if (!fetch_header_append(ctx, "\r\n", 2))
-				break;
-		}
-	}
-
-	/* FIXME: We'll just always add the end of headers mark now.
-	   mail-storage should rather include it in the header stream..
-	   however, not much of a problem since all non-broken mails have it.
-
-	   Also, Netscape 4.x seems to require this or it won't show the
-	   mail.. So if we do make this as RFC says, we'll need to add
-	   netscape-workaround. */
-	(void)fetch_header_append(ctx, "\r\n", 2);
-
-	message_parse_header_deinit(hdr_ctx);
-
-	i_assert(ctx->dest_size <= ctx->max_size);
-	i_assert(ctx->dest == NULL ||
-		 buffer_get_used_size(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, struct mail *mail,
-			     const struct imap_fetch_body_data *body,
-			     const char *header_section)
-{
-	struct fetch_header_field_context hdr_ctx;
-	const char *str;
-	const void *data;
-	size_t data_size;
-	uoff_t start_offset;
-	int failed;
-
-	/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-
-	if (strcmp(header_section, "HEADER") == 0) {
-		/* all headers */
-		return fetch_data(ctx, body, mail, input, 0, 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.mail = mail;
-	hdr_ctx.fetch_ctx = ctx;
-	hdr_ctx.skip = body->skip;
-	hdr_ctx.max_size = body->max_size;
-	hdr_ctx.fix_nuls = !mail->has_no_nuls;
-
-	failed = FALSE;
-	start_offset = input == NULL ? 0 : input->v_offset;
-
-	t_push();
-
-	/* first pass, we need at least the size */
-	if (!ctx->body_fetch_from_cache &&
-	    size->virtual_size > MAX_HEADER_BUFFER_SIZE &&
-	    body->max_size > MAX_HEADER_BUFFER_SIZE) {
-		if (!fetch_header_fields(input, header_section, &hdr_ctx))
-			failed = TRUE;
-
-		i_assert(hdr_ctx.dest_size <= size->virtual_size);
-	} else {
-		hdr_ctx.dest =
-			buffer_create_dynamic(pool_datastack_create(),
-					      I_MIN(size->virtual_size, 8192),
-					      (size_t)-1);
-		if (!fetch_header_fields(input, header_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, header_section,
-						 &hdr_ctx))
-				failed = TRUE;
-
-			i_assert(first_size == hdr_ctx.dest_size);
-		} else {
-			data = buffer_get_data(hdr_ctx.dest, &data_size);
-			if (o_stream_send(ctx->output, data, data_size) < 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;
-
-	if (ctx->body_fetch_from_cache) {
-		memset(&hdr_size, 0, sizeof(hdr_size));
-		stream = NULL;
-	} else {
-		stream = mail->get_stream(mail, &hdr_size, NULL);
-		if (stream == NULL)
-			return FALSE;
-	}
-
-	return fetch_header_from(ctx, stream, &hdr_size,
-				 mail, body, body->section);
-}
-
-/* Find message_part for section (eg. 1.3.4) */
-static int part_find(struct mail *mail, const struct imap_fetch_body_data *body,
-		     const struct message_part **part_r, const char **section)
-{
-	const struct message_part *part;
-	const char *path;
-	unsigned int num;
-
-	part = mail->get_parts(mail);
-	if (part == NULL)
-		return FALSE;
-
-	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 FALSE;
-			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)
-				part = NULL;
-		}
-
-		if (part != NULL &&
-		    (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) &&
-		    ((*path >= '0' && *path <= '9') ||
-		     strncmp(path, "HEADER", 6) == 0)) {
-			/* if remainder of path is a number or "HEADER",
-			   skip the message/rfc822 part */
-			part = part->children;
-		}
-	}
-
-	*part_r = part;
-	*section = path;
-	return TRUE;
-}
-
-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;
-
-	if (!part_find(mail, body, &part, &section))
-		return FALSE;
-
-	if (part == NULL) {
-		/* part doesn't exist */
-		return o_stream_send_str(ctx->output, ctx->prefix) > 0 &&
-			o_stream_send_str(ctx->output, " NIL") > 0;
-	}
-
-	stream = mail->get_stream(mail, NULL, NULL);
-	if (stream == NULL)
-		return FALSE;
-
-	if (*section == '\0' || strcmp(section, "TEXT") == 0) {
-		return fetch_data(ctx, body, mail, stream,
-				  part->physical_pos +
-				  part->header_size.physical_size,
-				  &part->body_size);
-	}
-
-	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,
-					 mail, body, section);
-	}
-
-	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-body.c	Sun Aug 15 06:40:30 2004 +0300
@@ -0,0 +1,838 @@
+/* Copyright (C) 2002-2004 Timo Sirainen */
+
+#include "common.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-header-filter.h"
+#include "message-parser.h"
+#include "message-send.h"
+#include "mail-storage.h"
+#include "imap-fetch.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+
+struct imap_fetch_body_data {
+	struct imap_fetch_body_data *next;
+
+        struct mailbox_header_lookup_ctx *header_ctx;
+	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 partial_cache {
+	unsigned int select_counter;
+	unsigned int uid;
+
+	uoff_t physical_start;
+	int cr_skipped;
+	struct message_size pos;
+};
+
+static struct partial_cache partial = { 0, 0, 0, 0, { 0, 0, 0 } };
+
+static const char *const *
+imap_fetch_get_body_fields(const char *fields, size_t *count_r)
+{
+	const char **arr, **p;
+	size_t count;
+
+	i_assert(*fields == '(');
+
+	arr = t_strsplit_spaces(t_strcut(fields+1, ')'), " ");
+	for (count = 0, p = arr; *p != NULL; p++)
+		count++;
+
+	qsort(arr, count, sizeof(*arr), strcasecmp_p);
+
+	*count_r = count;
+	return arr;
+}
+
+static int seek_partial(unsigned int select_counter, unsigned int uid,
+			struct partial_cache *partial, struct istream *stream,
+			uoff_t virtual_skip)
+{
+	int cr_skipped;
+
+	if (select_counter == partial->select_counter && uid == partial->uid &&
+	    stream->v_offset == partial->physical_start &&
+	    virtual_skip >= partial->pos.virtual_size) {
+		/* we can use the cache */
+		virtual_skip -= partial->pos.virtual_size;
+	} else {
+		partial->select_counter = select_counter;
+		partial->uid = uid;
+		partial->physical_start = stream->v_offset;
+		partial->cr_skipped = FALSE;
+		memset(&partial->pos, 0, sizeof(partial->pos));
+	}
+
+	i_stream_seek(stream, partial->physical_start +
+		      partial->pos.physical_size);
+	message_skip_virtual(stream, virtual_skip, &partial->pos,
+			     partial->cr_skipped, &cr_skipped);
+
+	partial->cr_skipped = FALSE;
+	return cr_skipped;
+}
+
+static uoff_t get_send_size(const struct imap_fetch_body_data *body,
+			    uoff_t max_size)
+{
+	uoff_t size;
+
+	if (body->skip >= max_size)
+		return 0;
+
+	size = max_size - body->skip;
+	return size <= body->max_size ? size : body->max_size;
+}
+
+static string_t *get_prefix(struct imap_fetch_context *ctx,
+			    const struct imap_fetch_body_data *body,
+			    uoff_t size)
+{
+	string_t *str;
+
+	str = t_str_new(128);
+	if (ctx->first)
+		ctx->first = FALSE;
+	else
+		str_append_c(str, ' ');
+
+	str_printfa(str, "BODY[%s]", body->section);
+	if (body->skip_set)
+		str_printfa(str, "<%"PRIuUOFF_T">", body->skip);
+
+	if (size != (uoff_t)-1)
+		str_printfa(str, " {%"PRIuUOFF_T"}\r\n", size);
+	else
+		str_append(str, " NIL");
+	return str;
+}
+
+static off_t imap_fetch_send(struct ostream *output, struct istream *input,
+			     int cr_skipped, uoff_t virtual_size, int *last_cr)
+{
+	const unsigned char *msg;
+	size_t i, size;
+	uoff_t vsize_left, sent;
+	off_t ret;
+	unsigned char add;
+	int blocks = FALSE;
+
+	/* go through the message data and insert CRs where needed.  */
+	sent = 0; vsize_left = virtual_size;
+	while (vsize_left > 0 && !blocks &&
+	       i_stream_read_data(input, &msg, &size, 0) > 0) {
+		add = '\0';
+		for (i = 0; i < size && vsize_left > 0; i++) {
+			vsize_left--;
+
+			if (msg[i] == '\n') {
+				if ((i > 0 && msg[i-1] != '\r') ||
+				    (i == 0 && !cr_skipped)) {
+					/* missing CR */
+					add = '\r';
+					break;
+				}
+			} else if (msg[i] == '\0') {
+				add = 128;
+				break;
+			}
+		}
+
+		if ((ret = o_stream_send(output, msg, i)) < 0)
+			return -1;
+		if (ret < i) {
+			add = '\0';
+			blocks = TRUE;
+		}
+		i_stream_skip(input, ret);
+		sent += ret;
+
+		cr_skipped = ret > 0 && msg[ret-1] == '\r';
+		if (add != '\0') {
+			if ((ret = o_stream_send(output, &add, 1)) < 0)
+				return -1;
+			if (ret == 0)
+				blocks = TRUE;
+			else {
+				sent++;
+				cr_skipped = add == '\r';
+				if (add == 128)
+					i_stream_skip(input, 1);
+			}
+		}
+	}
+
+	if ((uoff_t)sent != virtual_size && !blocks) {
+		/* Input stream gave less data then we expected. Two choices
+		   here: either we fill the missing data with spaces or we
+		   disconnect the client.
+
+		   We shouldn't really ever get here. One reason is if mail
+		   was deleted from NFS server while we were reading it.
+		   Another is some temporary disk error.
+
+		   If we filled the missing data the client could cache it,
+		   and if it was just a temporary error the message would be
+		   permanently left corrupted in client's local cache. So, we
+		   disconnect the client and hope that next try works. */
+		o_stream_close(output);
+		return -1;
+	}
+
+	*last_cr = cr_skipped;
+	return sent;
+}
+
+static int fetch_stream_send(struct imap_fetch_context *ctx)
+{
+	off_t ret;
+
+	o_stream_set_max_buffer_size(ctx->client->output, 0);
+	ret = imap_fetch_send(ctx->client->output, ctx->cur_input,
+			      ctx->skip_cr, ctx->cur_size - ctx->cur_offset,
+			      &ctx->skip_cr);
+	o_stream_set_max_buffer_size(ctx->client->output, (size_t)-1);
+
+	if (ret < 0)
+		return -1;
+
+	ctx->cur_offset += ret;
+	if (ctx->update_partial) {
+		partial.cr_skipped = ctx->skip_cr != 0;
+		partial.pos.physical_size =
+			ctx->cur_input->v_offset - partial.physical_start;
+		partial.pos.virtual_size += ret;
+	}
+
+	return ctx->cur_offset == ctx->cur_size;
+}
+
+static int fetch_stream_send_direct(struct imap_fetch_context *ctx)
+{
+	off_t ret;
+
+	o_stream_set_max_buffer_size(ctx->client->output, 0);
+	ret = o_stream_send_istream(ctx->client->output, ctx->cur_input);
+	o_stream_set_max_buffer_size(ctx->client->output, (size_t)-1);
+
+	if (ret < 0)
+		return -1;
+
+	ctx->cur_offset += ret;
+	return ctx->cur_offset == ctx->cur_size;
+}
+
+static int fetch_stream(struct imap_fetch_context *ctx,
+			const struct message_size *size)
+{
+	struct istream *input;
+
+	if (size->physical_size == size->virtual_size &&
+	    ctx->cur_mail->has_no_nuls) {
+		/* no need to kludge with CRs, we can use sendfile() */
+		input = i_stream_create_limit(default_pool, ctx->cur_input,
+					      ctx->cur_input->v_offset,
+					      ctx->cur_size);
+		i_stream_unref(ctx->cur_input);
+		ctx->cur_input = input;
+
+		ctx->cont_handler = fetch_stream_send_direct;
+	} else {
+                ctx->cont_handler = fetch_stream_send;
+	}
+
+	return ctx->cont_handler(ctx);
+}
+
+static int fetch_data(struct imap_fetch_context *ctx,
+		      const struct imap_fetch_body_data *body,
+		      const struct message_size *size)
+{
+	string_t *str;
+
+	ctx->cur_size = get_send_size(body, size->virtual_size);
+
+	str = get_prefix(ctx, body, ctx->cur_size);
+	if (o_stream_send(ctx->client->output,
+			  str_data(str), str_len(str)) < 0)
+		return -1;
+
+	if (!ctx->update_partial) {
+		message_skip_virtual(ctx->cur_input, body->skip, NULL, FALSE,
+				     &ctx->skip_cr);
+	} else {
+		ctx->skip_cr =
+			seek_partial(ctx->select_counter, ctx->cur_mail->uid,
+				     &partial, ctx->cur_input, body->skip);
+	}
+
+	return fetch_stream(ctx, size);
+}
+
+static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
+		      void *context)
+{
+	const struct imap_fetch_body_data *body = context;
+	const struct message_size *fetch_size;
+	struct message_size hdr_size, body_size;
+
+	ctx->cur_input = mail->get_stream(mail, &hdr_size, &body_size);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = TRUE;
+
+	switch (body->section[0]) {
+	case '\0':
+		/* BODY[] - fetch everything */
+		message_size_add(&body_size, &hdr_size);
+                fetch_size = &body_size;
+		break;
+	case 'H':
+		/* BODY[HEADER] - fetch only header */
+                fetch_size = &hdr_size;
+		break;
+	case 'T':
+		/* BODY[TEXT] - skip header */
+		i_stream_skip(ctx->cur_input, hdr_size.physical_size);
+                fetch_size = &body_size;
+		break;
+	default:
+		i_unreached();
+	}
+
+	return fetch_data(ctx, body, fetch_size);
+}
+
+static void header_filter_mime(struct message_header_line *hdr,
+			       int *matched, void *context __attr_unused__)
+{
+	if (hdr == NULL)
+		return;
+
+	*matched = strncasecmp(hdr->name, "Content-", 8) == 0 ||
+		strcasecmp(hdr->name, "Mime-Version") == 0;
+}
+
+static int fetch_header_partial_from(struct imap_fetch_context *ctx,
+				     const struct imap_fetch_body_data *body,
+				     const char *header_section)
+{
+	const char *const *fields;
+	struct message_size msg_size;
+	struct istream *input;
+	size_t size, fields_count;
+
+	/* MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+
+	if (strncmp(header_section, "HEADER.FIELDS ", 14) == 0) {
+		fields = imap_fetch_get_body_fields(header_section + 14,
+						    &fields_count);
+		input = i_stream_create_header_filter(ctx->client->cmd_pool,
+						      ctx->cur_input, FALSE,
+						      fields, fields_count,
+						      NULL, NULL);
+	} else if (strncmp(header_section, "HEADER.FIELDS.NOT ", 18) == 0) {
+		fields = imap_fetch_get_body_fields(header_section + 18,
+						    &fields_count);
+		input = i_stream_create_header_filter(ctx->client->cmd_pool,
+						      ctx->cur_input, TRUE,
+						      fields, fields_count,
+						      NULL, NULL);
+	} else if (strcmp(header_section, "MIME") == 0) {
+		/* Mime-Version + Content-* fields */
+		input = i_stream_create_header_filter(ctx->client->cmd_pool,
+						      ctx->cur_input, FALSE,
+						      NULL, 0,
+						      header_filter_mime, NULL);
+	} else {
+		i_error("BUG: Accepted invalid section from user: '%s'",
+			header_section);
+		return -1;
+	}
+
+	i_stream_unref(ctx->cur_input);
+	ctx->cur_input = input;
+	ctx->update_partial = FALSE;
+
+	/* FIXME: We'll just always add the end of headers mark now.
+	   mail-storage should rather include it in the header stream..
+	   however, not much of a problem since all non-broken mails have it.
+
+	   Also, Netscape 4.x seems to require this or it won't show the
+	   mail.. So if we do make this as RFC says, we'll need to add
+	   netscape-workaround. */
+
+	// FIXME: we rely on the current behavior of header filter..
+	(void)i_stream_get_data(ctx->cur_input, &size);
+	memset(&msg_size, 0, sizeof(msg_size));
+	msg_size.physical_size = msg_size.virtual_size = size;
+	return fetch_data(ctx, body, &msg_size);
+}
+
+static int fetch_body_header_partial(struct imap_fetch_context *ctx,
+				     struct mail *mail, void *context)
+{
+	const struct imap_fetch_body_data *body = context;
+
+	ctx->cur_input = mail->get_stream(mail, NULL, NULL);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = FALSE;
+
+	return fetch_header_partial_from(ctx, body, body->section);
+}
+
+static int fetch_body_header_fields(struct imap_fetch_context *ctx,
+				    struct mail *mail, void *context)
+{
+	const struct imap_fetch_body_data *body = context;
+	struct message_size size;
+
+	ctx->cur_input = mail->get_headers(mail, body->header_ctx);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = FALSE;
+
+	message_get_body_size(ctx->cur_input, &size, NULL);
+	i_stream_seek(ctx->cur_input, 0);
+
+	return fetch_data(ctx, body, &size);
+}
+
+/* Find message_part for section (eg. 1.3.4) */
+static int part_find(struct mail *mail, const struct imap_fetch_body_data *body,
+		     const struct message_part **part_r, const char **section)
+{
+	const struct message_part *part;
+	const char *path;
+	unsigned int num;
+
+	part = mail->get_parts(mail);
+	if (part == NULL)
+		return -1;
+
+	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 FALSE;
+			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)
+				part = NULL;
+		}
+
+		if (part != NULL &&
+		    (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) &&
+		    ((*path >= '0' && *path <= '9') ||
+		     strncmp(path, "HEADER", 6) == 0)) {
+			/* if remainder of path is a number or "HEADER",
+			   skip the message/rfc822 part */
+			part = part->children;
+		}
+	}
+
+	*part_r = part;
+	*section = path;
+	return 0;
+}
+
+static int fetch_body_mime(struct imap_fetch_context *ctx, struct mail *mail,
+			   void *context)
+{
+	const struct imap_fetch_body_data *body = context;
+	const struct message_part *part;
+	const char *section;
+
+	if (part_find(mail, body, &part, &section) < 0)
+		return -1;
+
+	if (part == NULL) {
+		/* part doesn't exist */
+		string_t *str = get_prefix(ctx, body, (uoff_t)-1);
+		if (o_stream_send(ctx->client->output,
+				  str_data(str), str_len(str)) < 0)
+			return -1;
+		return 1;
+	}
+
+	ctx->cur_input = mail->get_stream(mail, NULL, NULL);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = TRUE;
+
+	if (*section == '\0' || strcmp(section, "TEXT") == 0) {
+		i_stream_seek(ctx->cur_input, part->physical_pos +
+			      part->header_size.physical_size);
+		return fetch_data(ctx, body, &part->body_size);
+	}
+
+	if (strcmp(section, "HEADER") == 0) {
+		/* all headers */
+		return fetch_data(ctx, body, &part->header_size);
+	}
+
+	if (strncmp(section, "HEADER", 6) == 0 ||
+	    strcmp(section, "MIME") == 0) {
+		i_stream_seek(ctx->cur_input, part->physical_pos);
+		return fetch_header_partial_from(ctx, body, section);
+	}
+
+	i_error("BUG: Accepted invalid section from user: '%s'", body->section);
+	return 1;
+}
+
+static int fetch_body_header_fields_check(const char *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 fetch_body_header_fields_init(struct imap_fetch_context *ctx,
+					 struct imap_fetch_body_data *body,
+					 const char *section)
+{
+	const char *const *headers, *const *arr;
+	size_t count;
+
+	if (!fetch_body_header_fields_check(section))
+		return FALSE;
+
+	if ((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
+				MAIL_FETCH_STREAM_BODY)) != 0) {
+		/* we'll need to open the file anyway, don't try to get the
+		   headers from cache. */
+		imap_fetch_add_handler(ctx, fetch_body_header_partial, body);
+		return TRUE;
+	}
+
+	t_push();
+        headers = imap_fetch_get_body_fields(section, &count);
+
+	for (arr = headers; *arr != NULL; arr++) {
+		char *hdr = p_strdup(ctx->client->cmd_pool, *arr);
+		buffer_append(ctx->all_headers_buf, &hdr, sizeof(hdr));
+	}
+
+	body->header_ctx = mailbox_header_lookup_init(ctx->box, headers);
+	imap_fetch_add_handler(ctx, fetch_body_header_fields, body);
+	t_pop();
+	return TRUE;
+}
+
+static int fetch_body_section_name_init(struct imap_fetch_context *ctx,
+					struct imap_fetch_body_data *body)
+{
+	const char *section = body->section;
+
+	if (*section == '\0') {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+			MAIL_FETCH_STREAM_BODY;
+		imap_fetch_add_handler(ctx, fetch_body, body);
+		return TRUE;
+	}
+
+	if (strcmp(section, "TEXT") == 0) {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+		imap_fetch_add_handler(ctx, fetch_body, body);
+		return TRUE;
+	}
+
+	if (strncmp(section, "HEADER", 6) == 0) {
+		/* exact header matches could be cached */
+		if (section[6] == '\0') {
+			ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+			imap_fetch_add_handler(ctx, fetch_body, body);
+			return TRUE;
+		}
+
+		if (strncmp(section, "HEADER.FIELDS ", 14) == 0 &&
+		    fetch_body_header_fields_init(ctx, body, section+14))
+			return TRUE;
+
+		if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0 &&
+		    fetch_body_header_fields_check(section+18)) {
+			imap_fetch_add_handler(ctx, fetch_body_header_partial,
+					       body);
+			return TRUE;
+		}
+	} else if (*section >= '0' && *section <= '9') {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY |
+			MAIL_FETCH_MESSAGE_PARTS;
+
+		while ((*section >= '0' && *section <= '9') ||
+		       *section == '.') section++;
+
+		if (*section == '\0' ||
+		    strcmp(section, "MIME") == 0 ||
+		    strcmp(section, "TEXT") == 0 ||
+		    strcmp(section, "HEADER") == 0 ||
+		    (strncmp(section, "HEADER.FIELDS ", 14) == 0 &&
+		     fetch_body_header_fields_check(section+14)) ||
+		    (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0 &&
+		     fetch_body_header_fields_check(section+18))) {
+			imap_fetch_add_handler(ctx, fetch_body_mime, body);
+			return TRUE;
+		}
+	}
+
+	client_send_command_error(ctx->client,
+		"Invalid BODY[..] parameter: Unknown or broken section");
+	return FALSE;
+}
+
+/* Parse next digits in string into integer. Returns FALSE if the integer
+   becomes too big and wraps. */
+static int read_uoff_t(const char **p, uoff_t *value)
+{
+	uoff_t prev;
+
+	*value = 0;
+	while (**p >= '0' && **p <= '9') {
+		prev = *value;
+		*value = *value * 10 + (**p - '0');
+
+		if (*value < prev)
+			return FALSE;
+
+		(*p)++;
+	}
+
+	return TRUE;
+}
+
+int fetch_body_section_init(struct imap_fetch_context *ctx, const char *arg)
+{
+	struct imap_fetch_body_data *body;
+	const char *p = arg + 4;
+
+	body = p_new(ctx->client->cmd_pool, struct imap_fetch_body_data, 1);
+	body->max_size = (uoff_t)-1;
+
+	if (strncmp(p, ".PEEK", 5) == 0) {
+		body->peek = TRUE;
+		p += 5;
+	} else {
+		ctx->flags_update_seen = TRUE;
+	}
+
+	if (*p != '[') {
+		client_send_command_error(ctx->client,
+			"Invalid BODY[..] parameter: Missing '['");
+		return FALSE;
+	}
+
+	body->section = p+1;
+	p = strchr(body->section, ']');
+	if (p == NULL) {
+		client_send_command_error(ctx->client,
+			"Invalid BODY[..] parameter: Missing ']'");
+		return FALSE;
+	}
+	body->section = p_strdup_until(ctx->client->cmd_pool, body->section, p);
+
+	if (*++p == '<') {
+		/* <start.end> */
+		p++;
+		body->skip_set = TRUE;
+
+		if (!read_uoff_t(&p, &body->skip) || body->skip > OFF_T_MAX) {
+			/* wrapped */
+			client_send_command_error(ctx->client,
+				"Invalid BODY[..] parameter: "
+				"Too big partial start");
+			return FALSE;
+		}
+
+		if (*p == '.') {
+			p++;
+			if (!read_uoff_t(&p, &body->max_size) ||
+			    body->max_size > OFF_T_MAX) {
+				/* wrapped */
+				client_send_command_error(ctx->client,
+					"Invalid BODY[..] parameter: "
+					"Too big partial end");
+				return FALSE;
+			}
+		}
+
+		if (*p != '>') {
+			client_send_command_error(ctx->client,
+				"Invalid BODY[..] parameter: Missing '>'");
+			return FALSE;
+		}
+	}
+
+	return fetch_body_section_name_init(ctx, body);
+}
+
+static int fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail,
+			     void *context __attr_unused__)
+{
+	uoff_t size;
+
+	size = mail->get_size(mail);
+	if (size == (uoff_t)-1)
+		return -1;
+
+	str_printfa(ctx->cur_str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
+	return 1;
+}
+
+static int fetch_rfc822(struct imap_fetch_context *ctx, struct mail *mail,
+			void *context __attr_unused__)
+{
+	struct message_size hdr_size, body_size;
+	const char *str;
+
+	ctx->cur_input = mail->get_stream(mail, &hdr_size, &body_size);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = FALSE;
+
+	message_size_add(&body_size, &hdr_size);
+
+	if (ctx->cur_offset == 0) {
+		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->client->output, str) < 0)
+			return -1;
+	}
+
+        ctx->cur_size = body_size.virtual_size;
+	return fetch_stream(ctx, &body_size);
+}
+
+static int fetch_rfc822_header(struct imap_fetch_context *ctx,
+			       struct mail *mail, void *context __attr_unused__)
+{
+	struct message_size hdr_size;
+	const char *str;
+
+	ctx->cur_input = mail->get_stream(mail, &hdr_size, NULL);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = 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->client->output, str) < 0)
+		return -1;
+
+        ctx->cur_size = hdr_size.virtual_size;
+	return fetch_stream(ctx, &hdr_size);
+}
+
+static int fetch_rfc822_text(struct imap_fetch_context *ctx, struct mail *mail,
+			     void *context __attr_unused__)
+{
+	struct message_size hdr_size, body_size;
+	const char *str;
+
+	ctx->cur_input = mail->get_stream(mail, &hdr_size, &body_size);
+	if (ctx->cur_input == NULL)
+		return -1;
+
+	i_stream_ref(ctx->cur_input);
+	ctx->update_partial = 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->client->output, str) < 0)
+		return -1;
+
+	i_stream_seek(ctx->cur_input, hdr_size.physical_size);
+        ctx->cur_size = body_size.virtual_size;
+	return fetch_stream(ctx, &body_size);
+}
+
+int fetch_rfc822_init(struct imap_fetch_context *ctx, const char *arg)
+{
+	if (arg[6] == '\0') {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+			MAIL_FETCH_STREAM_BODY;
+		ctx->flags_update_seen = TRUE;
+		imap_fetch_add_handler(ctx, fetch_rfc822, NULL);
+		return TRUE;
+	}
+
+	if (strcmp(arg+6, ".SIZE") == 0) {
+		ctx->fetch_data |= MAIL_FETCH_SIZE;
+		imap_fetch_add_handler(ctx, fetch_rfc822_size, NULL);
+		return TRUE;
+	}
+	if (strcmp(arg+6, ".HEADER") == 0) {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+		imap_fetch_add_handler(ctx, fetch_rfc822_header, NULL);
+		return TRUE;
+	}
+	if (strcmp(arg+6, ".TEXT") == 0) {
+		ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+		ctx->flags_update_seen = TRUE;
+		imap_fetch_add_handler(ctx, fetch_rfc822_text, NULL);
+		return TRUE;
+	}
+
+	client_send_command_error(ctx->client, t_strconcat(
+		"Unknown parameter ", arg, NULL));
+	return FALSE;
+}
--- a/src/imap/imap-fetch.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-fetch.c	Sun Aug 15 06:40:30 2004 +0300
@@ -1,4 +1,4 @@
-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002-2004 Timo Sirainen */
 
 #include "common.h"
 #include "buffer.h"
@@ -12,419 +12,445 @@
 #include "imap-fetch.h"
 #include "imap-util.h"
 
-#include <unistd.h>
+#include <stdlib.h>
 
-const char *const *imap_fetch_get_body_fields(const char *fields)
+const struct imap_fetch_handler default_handlers[7];
+static buffer_t *fetch_handlers = NULL;
+
+static int imap_fetch_handler_cmp(const void *p1, const void *p2)
 {
-	const char **field_list, **field, **dest;
+        const struct imap_fetch_handler *h1 = p1, *h2 = p2;
+
+	return strcmp(h1->name, h2->name);
+}
+
+void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
+				  size_t count)
+{
+	void *data;
+	size_t size;
 
-	while (*fields == ' ')
-		fields++;
-	if (*fields == '(')
-		fields++;
+	if (fetch_handlers == NULL) {
+		fetch_handlers = buffer_create_dynamic(default_pool,
+						       128, (size_t)-1);
+	}
+	buffer_append(fetch_handlers, handlers, sizeof(*handlers) * count);
+
+	data = buffer_get_modifyable_data(fetch_handlers, &size);
+	qsort(data, size / sizeof(*handlers), sizeof(*handlers),
+	      imap_fetch_handler_cmp);
+}
 
-	field_list = t_strsplit_spaces(t_strcut(fields, ')'), " ");
+static int imap_fetch_handler_bsearch(const void *name_p, const void *handler_p)
+{
+	const char *name = name_p;
+        const struct imap_fetch_handler *h = handler_p;
+	int i;
+
+	for (i = 0; h->name[i] != '\0'; i++) {
+		if (h->name[i] != name[i]) {
+			if (name[i] < 'A' || name[i] >= 'Z')
+				return -1;
+			return name[i] - h->name[i];
+		}
+	}
 
-	/* array ends at ")" element */
-	for (field = dest = field_list; *field != NULL; field++) {
-		*dest = *field;
-		dest++;
-	}
-	*dest = NULL;
+	return name[i] < 'A' || name[i] >= 'Z' ? 0 : -1;
+}
+
+int imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *arg)
+{
+	const struct imap_fetch_handler *handler;
 
-	return field_list;
+	handler = bsearch(arg, fetch_handlers->data,
+			  fetch_handlers->used /
+			  sizeof(struct imap_fetch_handler),
+                          sizeof(struct imap_fetch_handler),
+			  imap_fetch_handler_bsearch);
+	if (handler == NULL)
+		i_panic("Called unknown handler: %s", arg);
+
+	return handler->init(ctx, arg);
 }
 
-static void fetch_uid(struct imap_fetch_context *ctx, struct mail *mail)
+struct imap_fetch_context *imap_fetch_init(struct client *client)
 {
-	str_printfa(ctx->str, "UID %u ", mail->uid);
+	struct imap_fetch_context *ctx;
+
+	if (fetch_handlers == NULL) {
+		imap_fetch_handlers_register(default_handlers,
+					     sizeof(default_handlers) /
+					     sizeof(default_handlers[0]));
+	}
+
+	ctx = p_new(client->cmd_pool, struct imap_fetch_context, 1);
+	ctx->client = client;
+	ctx->box = client->mailbox;
+
+	ctx->cur_str = str_new(default_pool, 8192);
+	ctx->seen_flag.flags = MAIL_SEEN;
+	ctx->all_headers_buf =
+		buffer_create_dynamic(client->cmd_pool, 128, (size_t)-1);
+	ctx->handlers =
+		buffer_create_dynamic(client->cmd_pool, 128, (size_t)-1);
+	return ctx;
 }
 
-static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail,
-		       const struct mail_full_flags *flags)
+void imap_fetch_add_handler(struct imap_fetch_context *ctx,
+			    imap_fetch_handler_t *handler, void *context)
 {
-	struct mail_full_flags full_flags;
+	const struct imap_fetch_context_handler *handlers;
+	struct imap_fetch_context_handler h;
+	size_t i, size;
+
+	if (context == NULL) {
+		/* don't allow duplicate handlers */
+		handlers = buffer_get_data(ctx->handlers, &size);
+		size /= sizeof(*handlers);
+
+		for (i = 0; i < size; i++) {
+			if (handlers[i].handler == handler &&
+			    handlers[i].context == NULL)
+				return;
+		}
+	}
 
-	if (flags == NULL) {
-		flags = mail->get_flags(mail);
-		if (flags == NULL)
-			return FALSE;
+	memset(&h, 0, sizeof(h));
+	h.handler = handler;
+	h.context = context;
+
+	buffer_append(ctx->handlers, &h, sizeof(h));
+}
+
+void imap_fetch_begin(struct imap_fetch_context *ctx,
+		      struct mail_search_arg *search_arg)
+{
+	const void *null = NULL;
+	const void *data;
+
+	if (ctx->flags_update_seen) {
+		if (mailbox_is_readonly(ctx->box))
+			ctx->flags_update_seen = FALSE;
+		else if (!ctx->flags_have_handler) {
+			ctx->flags_show_only_seen_changes = TRUE;
+			(void)imap_fetch_init_handler(ctx, "FLAGS");
+		}
 	}
 
-	if (ctx->update_seen) {
-		/* \Seen change isn't shown by get_flags() yet */
-		full_flags = *flags;
-		full_flags.flags |= MAIL_SEEN;
-		flags = &full_flags;
+	if (buffer_get_used_size(ctx->all_headers_buf) != 0 &&
+	    ((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
+				 MAIL_FETCH_STREAM_BODY)) == 0)) {
+		buffer_append(ctx->all_headers_buf, &null, sizeof(null));
+
+		data = buffer_get_data(ctx->all_headers_buf, NULL);
+		ctx->all_headers_ctx =
+			mailbox_header_lookup_init(ctx->box, data);
 	}
 
-	str_append(ctx->str, "FLAGS (");
-	imap_write_flags(ctx->str, flags);
-	str_append(ctx->str, ") ");
-	return TRUE;
+	ctx->trans = mailbox_transaction_begin(ctx->box, TRUE);
+	ctx->select_counter = ctx->client->select_counter;
+	ctx->search_ctx =
+		mailbox_search_init(ctx->trans, NULL, search_arg, NULL,
+				    ctx->fetch_data, ctx->all_headers_ctx);
 }
 
-static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail)
+int imap_fetch(struct imap_fetch_context *ctx)
 {
-	time_t time;
+	const struct imap_fetch_context_handler *handlers;
+	size_t size;
+	int ret;
+
+	if (ctx->cont_handler != NULL) {
+		if ((ret = ctx->cont_handler(ctx)) <= 0) {
+			if (ret < 0)
+				ctx->failed = TRUE;
+			return ret;
+		}
+
+		ctx->cont_handler = NULL;
+		ctx->cur_offset = 0;
+	}
+
+	handlers = buffer_get_data(ctx->handlers, &size);
+	size /= sizeof(*handlers);
+
+	for (;;) {
+		if (o_stream_get_buffer_used_size(ctx->client->output) >=
+		    CLIENT_OUTPUT_OPTIMAL_SIZE) {
+			ret = o_stream_flush(ctx->client->output);
+			if (ret <= 0)
+				return ret;
+		}
+
+		if (ctx->cur_mail == NULL) {
+			if (ctx->cur_input != NULL) {
+				i_stream_unref(ctx->cur_input);
+                                ctx->cur_input = NULL;
+			}
+
+			ctx->cur_mail = mailbox_search_next(ctx->search_ctx);
+			if (ctx->cur_mail == NULL)
+				break;
 
-	time = mail->get_received_date(mail);
-	if (time == (time_t)-1)
-		return FALSE;
+			str_printfa(ctx->cur_str, "* %u FETCH (",
+				    ctx->cur_mail->seq);
+			if (o_stream_send(ctx->client->output,
+					  str_data(ctx->cur_str),
+					  str_len(ctx->cur_str)) < 0) {
+				ctx->failed = TRUE;
+				return -1;
+			}
+
+			str_truncate(ctx->cur_str, 0);
+			str_append_c(ctx->cur_str, ' ');
+			ctx->first = TRUE;
+		}
+
+		for (; ctx->cur_handler < size; ctx->cur_handler++) {
+			t_push();
+			ret = handlers[ctx->cur_handler].
+				handler(ctx, ctx->cur_mail,
+					handlers[ctx->cur_handler].context);
+			t_pop();
 
-	str_printfa(ctx->str, "INTERNALDATE \"%s\" ", imap_to_datetime(time));
-	return TRUE;
+			if (ret <= 0) {
+				if (ret < 0)
+					ctx->failed = TRUE;
+				else
+					i_assert(ctx->cont_handler != NULL);
+				return ret;
+			}
+
+			ctx->cont_handler = NULL;
+			ctx->cur_offset = 0;
+		}
+
+		if (str_len(ctx->cur_str) > 1) {
+			if (o_stream_send(ctx->client->output,
+					  str_data(ctx->cur_str) + ctx->first,
+					  str_len(ctx->cur_str) - 1 -
+					  ctx->first) < 0) {
+				ctx->failed = TRUE;
+				return -1;
+			}
+			str_truncate(ctx->cur_str, 0);
+		}
+
+		if (o_stream_send(ctx->client->output, ")\r\n", 3) < 0) {
+			ctx->failed = TRUE;
+			return -1;
+		}
+
+		ctx->cur_mail = NULL;
+		ctx->cur_handler = 0;
+	}
+
+	return 1;
 }
 
-static int fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail)
+int imap_fetch_deinit(struct imap_fetch_context *ctx)
 {
-	uoff_t size;
+	str_free(ctx->cur_str);
+
+	if (ctx->cur_input != NULL) {
+		i_stream_unref(ctx->cur_input);
+		ctx->cur_input = NULL;
+	}
 
-	size = mail->get_size(mail);
-	if (size == (uoff_t)-1)
-		return FALSE;
+	if (ctx->search_ctx != NULL) {
+		if (mailbox_search_deinit(ctx->search_ctx) < 0)
+			ctx->failed = TRUE;
+	}
+	if (ctx->all_headers_ctx != NULL)
+		mailbox_header_lookup_deinit(ctx->all_headers_ctx);
 
-	str_printfa(ctx->str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
-	return TRUE;
+	if (ctx->trans != NULL) {
+		if (ctx->failed)
+			mailbox_transaction_rollback(ctx->trans);
+		else {
+			if (mailbox_transaction_commit(ctx->trans) < 0)
+				ctx->failed = TRUE;
+		}
+	}
+	return ctx->failed ? -1 : 0;
 }
 
-static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail)
+static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
+		      void *context __attr_unused__)
 {
 	const char *body;
 
 	body = mail->get_special(mail, MAIL_FETCH_IMAP_BODY);
 	if (body == NULL)
-		return FALSE;
+		return -1;
 
-	if (ctx->first) {
-		if (o_stream_send_str(ctx->output, "BODY (") < 0)
-			return FALSE;
+	if (ctx->first)
 		ctx->first = FALSE;
-	} else {
-		if (o_stream_send_str(ctx->output, " BODY (") < 0)
-			return FALSE;
+	else {
+		if (o_stream_send(ctx->client->output, " ", 1) < 0)
+			return -1;
 	}
 
-	if (o_stream_send_str(ctx->output, body) < 0)
-		return FALSE;
+	if (o_stream_send(ctx->client->output, "BODY (", 6) < 0 ||
+	    o_stream_send_str(ctx->client->output, body) < 0 ||
+	    o_stream_send(ctx->client->output, ")", 1) < 0)
+		return -1;
+	return 1;
+}
 
-	if (o_stream_send(ctx->output, ")", 1) < 0)
-		return FALSE;
-	return TRUE;
+static int fetch_body_init(struct imap_fetch_context *ctx, const char *arg)
+{
+	if (arg[4] == '\0') {
+		ctx->fetch_data |= MAIL_FETCH_IMAP_BODY;
+		imap_fetch_add_handler(ctx, fetch_body, NULL);
+		return TRUE;
+	}
+	return fetch_body_section_init(ctx, arg);
 }
 
 static int fetch_bodystructure(struct imap_fetch_context *ctx,
-			       struct mail *mail)
+			       struct mail *mail, void *context __attr_unused__)
 {
 	const char *bodystructure;
 
 	bodystructure = mail->get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE);
 	if (bodystructure == NULL)
-		return FALSE;
+		return -1;
 
-	if (ctx->first) {
-		if (o_stream_send_str(ctx->output, "BODYSTRUCTURE (") < 0)
-			return FALSE;
+	if (ctx->first)
 		ctx->first = FALSE;
-	} else {
-		if (o_stream_send_str(ctx->output, " BODYSTRUCTURE (") < 0)
-			return FALSE;
+	else {
+		if (o_stream_send(ctx->client->output, " ", 1) < 0)
+			return -1;
 	}
 
-	if (o_stream_send_str(ctx->output, bodystructure) < 0)
-		return FALSE;
+	if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0 ||
+	    o_stream_send_str(ctx->client->output, bodystructure) < 0 ||
+	    o_stream_send(ctx->client->output, ")", 1) < 0)
+		return -1;
 
-	if (o_stream_send(ctx->output, ")", 1) < 0)
-		return FALSE;
+	return 1;
+}
+
+static int fetch_bodystructure_init(struct imap_fetch_context *ctx,
+				    const char *arg __attr_unused__)
+{
+	ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
+	imap_fetch_add_handler(ctx, fetch_bodystructure, NULL);
 	return TRUE;
 }
 
-static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail)
+static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail,
+			  void *context __attr_unused__)
 {
 	const char *envelope;
 
 	envelope = mail->get_special(mail, MAIL_FETCH_IMAP_ENVELOPE);
 	if (envelope == NULL)
-		return FALSE;
+		return -1;
 
-	if (ctx->first) {
-		if (o_stream_send_str(ctx->output, "ENVELOPE (") < 0)
-			return FALSE;
+	if (ctx->first)
 		ctx->first = FALSE;
-	} else {
-		if (o_stream_send_str(ctx->output, " ENVELOPE (") < 0)
-			return FALSE;
+	else {
+		if (o_stream_send(ctx->client->output, " ", 1) < 0)
+			return -1;
 	}
 
-	if (o_stream_send_str(ctx->output, envelope) < 0)
-		return FALSE;
+	if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0 ||
+	    o_stream_send_str(ctx->client->output, envelope) < 0 ||
+	    o_stream_send(ctx->client->output, ")", 1) < 0)
+		return -1;
+	return 1;
+}
 
-	if (o_stream_send(ctx->output, ")", 1) < 0)
-		return FALSE;
+static int fetch_envelope_init(struct imap_fetch_context *ctx,
+			       const char *arg __attr_unused__)
+{
+	ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
+	imap_fetch_add_handler(ctx, fetch_envelope, NULL);
 	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, body_size.virtual_size, NULL,
-			    !mail->has_no_nuls) >= 0;
-}
-
-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, hdr_size.virtual_size, NULL,
-			    !mail->has_no_nuls) >= 0;
-}
-
-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, body_size.virtual_size, NULL,
-			    !mail->has_no_nuls) >= 0;
-}
-
-static int fetch_mail(struct imap_fetch_context *ctx, struct mail *mail)
+static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail,
+		       void *context __attr_unused__)
 {
 	const struct mail_full_flags *flags;
-	struct imap_fetch_body_data *body;
-	size_t len, orig_len;
-	int failed, data_written, seen_updated = FALSE;
+	struct mail_full_flags full_flags;
+
+	flags = mail->get_flags(mail);
+	if (flags == NULL)
+		return -1;
 
-	if (!ctx->update_seen)
-		flags = NULL;
-	else {
-		flags = mail->get_flags(mail);
-		if (flags == NULL)
-			return FALSE;
+	if (ctx->flags_update_seen && (flags->flags & MAIL_SEEN) == 0) {
+		/* Add \Seen flag */
+		full_flags = *flags;
+		full_flags.flags |= MAIL_SEEN;
+		flags = &full_flags;
 
-		if ((flags->flags & MAIL_SEEN) == 0) {
-			if (mail->update_flags(mail, &ctx->seen_flag,
-					       MODIFY_ADD) < 0)
-				return FALSE;
-			seen_updated = TRUE;
-		}
+		if (mail->update_flags(mail, &ctx->seen_flag, MODIFY_ADD) < 0)
+			return -1;
+	} else if (ctx->flags_show_only_seen_changes) {
+		return 1;
 	}
 
-	t_push();
-
-	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) || seen_updated)
-			if (!fetch_flags(ctx, mail, flags))
-				break;
-		if (ctx->fetch_data & MAIL_FETCH_RECEIVED_DATE)
-			if (!fetch_internaldate(ctx, mail))
-				break;
-		if (ctx->fetch_data & MAIL_FETCH_SIZE)
-			if (!fetch_rfc822_size(ctx, mail))
-				break;
-
-		/* 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;
+	str_append(ctx->cur_str, "FLAGS (");
+	imap_write_flags(ctx->cur_str, flags);
+	str_append(ctx->cur_str, ") ");
+	return 1;
+}
 
-		/* medium size data .. seems to be faster without
-		 putting through string */
-		if (ctx->fetch_data & MAIL_FETCH_IMAP_BODY)
-			if (!fetch_body(ctx, mail))
-				break;
-		if (ctx->fetch_data & MAIL_FETCH_IMAP_BODYSTRUCTURE)
-			if (!fetch_bodystructure(ctx, mail))
-				break;
-		if (ctx->fetch_data & MAIL_FETCH_IMAP_ENVELOPE)
-			if(!fetch_envelope(ctx, mail))
-				break;
+static int fetch_flags_init(struct imap_fetch_context *ctx,
+			    const char *arg __attr_unused__)
+{
+	ctx->flags_have_handler = TRUE;
+	ctx->fetch_data |= MAIL_FETCH_FLAGS;
+	imap_fetch_add_handler(ctx, fetch_flags, NULL);
+	return 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;
-
-		failed = FALSE;
+static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail,
+			      void *context __attr_unused__)
+{
+	time_t time;
 
-		for (body = ctx->bodies; body != NULL; body = body->next) {
-			if (!imap_fetch_body_section(ctx, body, mail)) {
-				failed = TRUE;
-				break;
-			}
-		}
-	} while (0);
+	time = mail->get_received_date(mail);
+	if (time == (time_t)-1)
+		return -1;
 
-	if (data_written) {
-		if (o_stream_send(ctx->output, ")\r\n", 3) < 0)
-			failed = TRUE;
-	}
-
-	t_pop();
-	return !failed;
+	str_printfa(ctx->cur_str, "INTERNALDATE \"%s\" ",
+		    imap_to_datetime(time));
+	return 1;
 }
 
-int imap_fetch(struct client *client,
-	       enum mail_fetch_field fetch_data,
-	       enum imap_fetch_field imap_data,
-	       struct imap_fetch_body_data *bodies,
-	       struct mail_search_arg *search_args)
-{
-	struct mailbox *box = client->mailbox;
-	struct imap_fetch_context ctx;
-	struct mailbox_transaction_context *t;
-	struct mail *mail;
-	struct imap_fetch_body_data *body;
-	const char *null = NULL;
-	const char *const *arr;
-	buffer_t *buffer;
 
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.fetch_data = fetch_data;
-	ctx.imap_data = imap_data;
-	ctx.bodies = bodies;
-	ctx.output = client->output;
-	ctx.select_counter = client->select_counter;
-	ctx.seen_flag.flags = MAIL_SEEN;
+static int fetch_internaldate_init(struct imap_fetch_context *ctx,
+				   const char *arg __attr_unused__)
+{
+	ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE;
+	imap_fetch_add_handler(ctx, fetch_internaldate, NULL);
+	return TRUE;
+}
 
-	if (!mailbox_is_readonly(box)) {
-		/* If we have any BODY[..] sections, \Seen flag is added for
-		   all messages. */
-		for (body = bodies; body != NULL; body = body->next) {
-			if (!body->peek) {
-				ctx.update_seen = TRUE;
-				break;
-			}
-		}
-
-		if (imap_data & (IMAP_FETCH_RFC822|IMAP_FETCH_RFC822_TEXT))
-			ctx.update_seen = TRUE;
-	}
+static int fetch_uid(struct imap_fetch_context *ctx, struct mail *mail,
+		     void *context __attr_unused__)
+{
+	str_printfa(ctx->cur_str, "UID %u ", mail->uid);
+	return 1;
+}
 
-	/* If we have only BODY[HEADER.FIELDS (...)] fetches, get them
-	   separately rather than parsing the full header so mail storage
-	   can try to cache them. */
-	ctx.body_fetch_from_cache = (imap_data & (IMAP_FETCH_RFC822 |
-						  IMAP_FETCH_RFC822_HEADER |
-						  IMAP_FETCH_RFC822_TEXT)) == 0;
-	if (ctx.body_fetch_from_cache) {
-		buffer = buffer_create_dynamic(pool_datastack_create(),
-					       64, (size_t)-1);
-		for (body = bodies; body != NULL; body = body->next) {
-			if (strncmp(body->section, "HEADER.FIELDS ", 14) != 0) {
-				ctx.body_fetch_from_cache = FALSE;
-				break;
-			}
-
-			arr = imap_fetch_get_body_fields(body->section + 14);
-			while (*arr != NULL) {
-				buffer_append(buffer, arr, sizeof(*arr));
-				arr++;
-			}
-		}
-		buffer_append(buffer, &null, sizeof(null));
-		ctx.headers_ctx = !ctx.body_fetch_from_cache ? NULL :
-			mailbox_header_lookup_init(box, buffer_get_data(buffer,
-									NULL));
-	}
+static int fetch_uid_init(struct imap_fetch_context *ctx __attr_unused__,
+			  const char *arg __attr_unused__)
+{
+	imap_fetch_add_handler(ctx, fetch_uid, NULL);
+	return TRUE;
+}
 
-	t = mailbox_transaction_begin(box, TRUE);
-	ctx.search_ctx = mailbox_search_init(t, NULL, search_args, NULL,
-					     fetch_data, ctx.headers_ctx);
-	if (ctx.search_ctx == NULL)
-		ctx.failed = TRUE;
-	else {
-		ctx.str = str_new(default_pool, 8192);
-		while ((mail = mailbox_search_next(ctx.search_ctx)) != NULL) {
-			if (!fetch_mail(&ctx, mail)) {
-				ctx.failed = TRUE;
-				break;
-			}
-		}
-		str_free(ctx.str);
-
-		if (mailbox_search_deinit(ctx.search_ctx) < 0)
-			ctx.failed = TRUE;
-	}
-	if (ctx.headers_ctx != NULL)
-		mailbox_header_lookup_deinit(ctx.headers_ctx);
-
-	if (ctx.failed)
-		mailbox_transaction_rollback(t);
-	else {
-		if (mailbox_transaction_commit(t) < 0)
-			ctx.failed = TRUE;
-	}
-	return ctx.failed ? -1 : 0;
-}
+const struct imap_fetch_handler default_handlers[7] = {
+	{ "BODY", fetch_body_init },
+	{ "BODYSTRUCTURE", fetch_bodystructure_init },
+	{ "ENVELOPE", fetch_envelope_init },
+	{ "FLAGS", fetch_flags_init },
+	{ "INTERNALDATE", fetch_internaldate_init },
+	{ "RFC822", fetch_rfc822_init },
+	{ "UID", fetch_uid_init }
+};
--- a/src/imap/imap-fetch.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-fetch.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,52 +1,73 @@
 #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_context;
+
+/* Returns 1 = ok, 0 = client output buffer full, call again, -1 = error.
+   mail = NULL for deinit. */
+typedef int imap_fetch_handler_t(struct imap_fetch_context *ctx,
+				 struct mail *mail, void *context);
+
+struct imap_fetch_handler {
+	const char *name;
+
+	/* Returns FALSE if arg is invalid. */
+	int (*init)(struct imap_fetch_context *ctx, const char *arg);
 };
 
-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_handler {
+	imap_fetch_handler_t *handler;
+	void *context;
 };
 
 struct imap_fetch_context {
+	struct client *client;
+	struct mailbox *box;
+
+	struct mailbox_transaction_context *trans;
 	struct mail_search_context *search_ctx;
 
 	enum mail_fetch_field fetch_data;
-	enum imap_fetch_field imap_data;
-	struct imap_fetch_body_data *bodies;
-	struct mailbox_header_lookup_ctx *headers_ctx;
+	buffer_t *all_headers_buf;
+        struct mailbox_header_lookup_ctx *all_headers_ctx;
+
+	buffer_t *handlers;
 
-	string_t *str;
-	struct ostream *output;
-	const char *prefix;
+	struct mail *cur_mail;
+	unsigned int cur_handler;
+	uoff_t cur_size, cur_offset;
+	string_t *cur_str;
+	struct istream *cur_input;
+	int skip_cr;
+	int (*cont_handler)(struct imap_fetch_context *ctx);
+
 	unsigned int select_counter;
 
-	int update_seen;
 	struct mail_full_flags seen_flag;
 
-	int first, failed, body_fetch_from_cache;
+	unsigned int flags_have_handler:1;
+	unsigned int flags_update_seen:1;
+	unsigned int flags_show_only_seen_changes:1;
+	unsigned int update_partial:1;
+	unsigned int first:1;
+	unsigned int failed:1;
 };
 
-int imap_fetch(struct client *client,
-	       enum mail_fetch_field fetch_data,
-	       enum imap_fetch_field imap_data,
-	       struct imap_fetch_body_data *bodies,
-	       struct mail_search_arg *search_args);
+void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
+				  size_t count);
+
+void imap_fetch_add_handler(struct imap_fetch_context *ctx,
+			    imap_fetch_handler_t *handler, void *context);
 
-int imap_fetch_body_section(struct imap_fetch_context *ctx,
-			    const struct imap_fetch_body_data *body,
-			    struct mail *mail);
+struct imap_fetch_context *imap_fetch_init(struct client *client);
+int imap_fetch_deinit(struct imap_fetch_context *ctx);
+int imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *arg);
 
-const char *const *imap_fetch_get_body_fields(const char *fields);
+void imap_fetch_begin(struct imap_fetch_context *ctx,
+		      struct mail_search_arg *search_arg);
+int imap_fetch(struct imap_fetch_context *ctx);
+
+int fetch_body_section_init(struct imap_fetch_context *ctx, const char *arg);
+int fetch_rfc822_init(struct imap_fetch_context *ctx, const char *arg);
 
 #endif
--- a/src/imap/imap-messageset.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-messageset.c	Sun Aug 15 06:40:30 2004 +0300
@@ -28,7 +28,8 @@
 	return num;
 }
 
-struct mail_search_seqset *imap_messageset_parse(const char *messageset)
+struct mail_search_seqset *
+imap_messageset_parse(pool_t pool, const char *messageset)
 {
         struct mail_search_seqset *ret, **next;
 	uint32_t seq1, seq2;
@@ -75,7 +76,7 @@
 			seq2 = temp;
 		}
 
-		*next = t_new(struct mail_search_seqset, 1);
+		*next = p_new(pool, struct mail_search_seqset, 1);
 		(*next)->seq1 = seq1;
 		(*next)->seq2 = seq2;
 		next = &(*next)->next;
--- a/src/imap/imap-messageset.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-messageset.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,6 +1,7 @@
 #ifndef __IMAP_MESSAGESET_H
 #define __IMAP_MESSAGESET_H
 
-struct mail_search_seqset *imap_messageset_parse(const char *messageset);
+struct mail_search_seqset *
+imap_messageset_parse(pool_t pool, const char *messageset);
 
 #endif
--- a/src/imap/imap-search.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-search.c	Sun Aug 15 06:40:30 2004 +0300
@@ -14,13 +14,13 @@
 };
 
 static int
-imap_uidset_parse(struct mailbox *box, const char *uidset,
+imap_uidset_parse(pool_t pool, struct mailbox *box, const char *uidset,
 		  struct mail_search_seqset **seqset_r, const char **error_r)
 {
 	struct mail_search_seqset *seqset, **p;
 	int syntax, last;
 
-	*seqset_r = imap_messageset_parse(uidset);
+	*seqset_r = imap_messageset_parse(pool, uidset);
 	if (*seqset_r == NULL) {
 		*error_r = "Invalid UID messageset";
 		return -1;
@@ -99,11 +99,11 @@
 		return FALSE;
 	}
 
-	sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
+	sarg->value.str = str_ucase(p_strdup(data->pool, IMAP_ARG_STR(*args)));
 	*args += 1;
 
 	if (hdr_name != NULL)
-                sarg->hdr_field_name = hdr_name;
+                sarg->hdr_field_name = p_strdup(data->pool, hdr_name);
 
 	return TRUE;
 }
@@ -328,7 +328,7 @@
 			if (!ARG_NEW(SEARCH_SEQSET))
 				return FALSE;
 
-			return imap_uidset_parse(data->box,
+			return imap_uidset_parse(data->pool, data->box,
 						 (*next_sarg)->value.str,
 						 &(*next_sarg)->value.seqset,
 						 &data->error) == 0;
@@ -367,7 +367,7 @@
 	default:
 		if (*str == '*' || (*str >= '0' && *str <= '9')) {
 			/* <message-set> */
-			seqset = imap_messageset_parse(str);
+			seqset = imap_messageset_parse(data->pool, str);
 			if (seqset == NULL) {
 				data->error = "Invalid messageset";
 				return FALSE;
@@ -412,15 +412,15 @@
 	return first_sarg;
 }
 
-int imap_search_get_msgset_arg(const char *messageset,
-			       struct mail_search_arg **arg_r,
-			       const char **error_r)
+static int imap_search_get_msgset_arg(pool_t pool, const char *messageset,
+				      struct mail_search_arg **arg_r,
+				      const char **error_r)
 {
 	struct mail_search_arg *arg;
 
-	arg = t_new(struct mail_search_arg, 1);
+	arg = p_new(pool, struct mail_search_arg, 1);
 	arg->type = SEARCH_SEQSET;
-	arg->value.seqset = imap_messageset_parse(messageset);
+	arg->value.seqset = imap_messageset_parse(pool, messageset);
 	if (arg->value.seqset == NULL) {
 		*error_r = "Invalid messageset";
 		return -1;
@@ -429,16 +429,17 @@
 	return 0;
 }
 
-int imap_search_get_uidset_arg(struct mailbox *box, const char *uidset,
-			       struct mail_search_arg **arg_r,
-			       const char **error_r)
+static int
+imap_search_get_uidset_arg(pool_t pool, struct mailbox *box, const char *uidset,
+			   struct mail_search_arg **arg_r, const char **error_r)
 {
 	struct mail_search_arg *arg;
 
-	arg = t_new(struct mail_search_arg, 1);
+	arg = p_new(pool, struct mail_search_arg, 1);
 	arg->type = SEARCH_SEQSET;
 	*arg_r = arg;
-	return imap_uidset_parse(box, uidset, &arg->value.seqset, error_r);
+	return imap_uidset_parse(pool, box, uidset, &arg->value.seqset,
+				 error_r);
 }
 
 struct mail_search_arg *
@@ -449,14 +450,15 @@
 	int ret;
 
 	if (!uid) {
-		ret = imap_search_get_msgset_arg(set, &search_arg,
-						 &error);
+		ret = imap_search_get_msgset_arg(client->cmd_pool, set,
+						 &search_arg, &error);
 	} else {
-		ret = imap_search_get_uidset_arg(client->mailbox, set,
+		ret = imap_search_get_uidset_arg(client->cmd_pool,
+						 client->mailbox, set,
 						 &search_arg, &error);
 	}
 	if (ret < 0) {
-		client_send_tagline(client, t_strconcat("BAD ", error, NULL));
+		client_send_command_error(client, error);
 		return NULL;
 	}
 
--- a/src/imap/imap-search.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-search.h	Sun Aug 15 06:40:30 2004 +0300
@@ -10,12 +10,6 @@
 imap_search_args_build(pool_t pool, struct mailbox *box, struct imap_arg *args,
 		       const char **error_r);
 
-int imap_search_get_msgset_arg(const char *messageset,
-			       struct mail_search_arg **arg_r,
-			       const char **error_r);
-int imap_search_get_uidset_arg(struct mailbox *box, const char *uidset,
-			       struct mail_search_arg **arg_r,
-			       const char **error_r);
 struct mail_search_arg *
 imap_search_get_arg(struct client *client, const char *set, int uid);
 
--- a/src/imap/imap-thread.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/imap-thread.c	Sun Aug 15 06:40:30 2004 +0300
@@ -849,8 +849,8 @@
 	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)))
+			if (o_stream_send(ctx->output,
+					  str_data(str), str_len(str)) < 0)
 				return FALSE;
 			str_truncate(str, 0);
 		}
@@ -886,8 +886,8 @@
 
 		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)))
+			if (o_stream_send(ctx->output,
+					  str_data(str), str_len(str)) < 0)
 				return;
 			str_truncate(str, 0);
 		}
--- a/src/imap/mail-storage-callbacks.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/mail-storage-callbacks.c	Sun Aug 15 06:40:30 2004 +0300
@@ -19,18 +19,30 @@
 		      const char *text, void *context)
 {
 	struct client *client = context;
+	const char *str;
 
-	client_send_line(client, t_strconcat("* OK ", text, NULL));
-	o_stream_flush(client->output);
+	if (o_stream_get_buffer_used_size(client->output) != 0)
+		return;
+
+	t_push();
+	str = t_strconcat("* OK ", text, "\r\n", NULL);
+	o_stream_send_str(client->output, str);
+	t_pop();
 }
 
 static void notify_no(struct mailbox *mailbox __attr_unused__,
 		      const char *text, void *context)
 {
 	struct client *client = context;
+	const char *str;
 
-	client_send_line(client, t_strconcat("* NO ", text, NULL));
-	o_stream_flush(client->output);
+	if (o_stream_get_buffer_used_size(client->output) != 0)
+		return;
+
+	t_push();
+	str = t_strconcat("* NO ", text, "\r\n", NULL);
+	o_stream_send_str(client->output, str);
+	t_pop();
 }
 
 struct mail_storage_callbacks mail_storage_callbacks = {
--- a/src/imap/main.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/imap/main.c	Sun Aug 15 06:40:30 2004 +0300
@@ -176,7 +176,7 @@
 		client_send_line(client, t_strconcat(getenv("IMAPLOGINTAG"),
 						     " OK Logged in.", NULL));
 	}
-        o_stream_flush(client->output);
+        o_stream_uncork(client->output);
 }
 
 static void main_deinit(void)
--- a/src/lib-auth/auth-server-connection.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-auth/auth-server-connection.c	Sun Aug 15 06:40:30 2004 +0300
@@ -194,7 +194,7 @@
 	}
 	conn->input = i_stream_create_file(fd, default_pool, MAX_INBUF_SIZE,
 					   FALSE);
-	conn->output = o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE,
+	conn->output = o_stream_create_file(fd, default_pool, (size_t)-1,
 					    FALSE);
 	conn->requests = hash_create(default_pool, pool, 100, NULL, NULL);
 
--- a/src/lib-auth/auth-server-request.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-auth/auth-server-request.c	Sun Aug 15 06:40:30 2004 +0300
@@ -75,7 +75,7 @@
 	struct auth_client_request_new auth_request;
 	buffer_t *buf;
 	size_t size;
-	int ret;
+	ssize_t ret;
 
 	memset(&auth_request, 0, sizeof(auth_request));
 	auth_request.type = AUTH_CLIENT_REQUEST_NEW;
@@ -136,15 +136,19 @@
 				      const unsigned char *data, size_t size)
 {
 	struct auth_client_request_continue auth_request;
+        struct const_iovec iov[2];
 
 	/* send continued request to auth */
 	auth_request.type = AUTH_CLIENT_REQUEST_CONTINUE;
 	auth_request.id = request->id;
 	auth_request.data_size = size;
 
-	if (o_stream_send(conn->output, &auth_request,
-			  sizeof(auth_request)) < 0 ||
-	    o_stream_send(conn->output, data, size) < 0) {
+	iov[0].iov_base = &auth_request;
+	iov[0].iov_len = sizeof(auth_request);
+	iov[1].iov_base = data;
+	iov[1].iov_len = size;
+
+	if (o_stream_sendv(conn->output, iov, 2) < 0) {
 		errno = conn->output->stream_errno;
 		i_warning("Error sending continue request to auth server: %m");
 		auth_server_connection_destroy(conn, TRUE);
--- a/src/lib-mail/istream-header-filter.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-mail/istream-header-filter.c	Sun Aug 15 06:40:30 2004 +0300
@@ -41,16 +41,6 @@
 	i_stream_set_max_buffer_size(mstream->input, max_size);
 }
 
-static void _set_blocking(struct _iostream *stream, int timeout_msecs,
-			  void (*timeout_cb)(void *), void *context)
-{
-	struct header_filter_istream *mstream =
-		(struct header_filter_istream *)stream;
-
-	i_stream_set_blocking(mstream->input, timeout_msecs,
-			      timeout_cb, context);
-}
-
 static ssize_t _read(struct _istream *stream)
 {
 	struct header_filter_istream *mstream =
@@ -136,7 +126,7 @@
 		matched = bsearch(hdr->name, headers, headers_count,
 				  sizeof(*headers), bsearch_strcasecmp) != NULL;
 		if (callback != NULL)
-			callback(hdr, matched, context);
+			callback(hdr, &matched, context);
 
 		if (matched == filter) {
 			/* ignore */
@@ -188,7 +178,6 @@
 	mstream->istream.iostream.close = _close;
 	mstream->istream.iostream.destroy = _destroy;
 	mstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	mstream->istream.iostream.set_blocking = _set_blocking;
 
 	mstream->istream.read = _read;
 	mstream->istream.seek = _seek;
--- a/src/lib-mail/istream-header-filter.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-mail/istream-header-filter.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,8 +1,10 @@
 #ifndef __ISTREAM_HEADER_FILTER_H
 #define __ISTREAM_HEADER_FILTER_H
 
+struct message_header_line;
+
 typedef void header_filter_callback(struct message_header_line *hdr,
-				    int matched, void *context);
+				    int *matched, void *context);
 
 /* NOTE: headers list must be sorted. If filter is TRUE, given headers are
    removed from output, otherwise only given headers are included in output. */
--- a/src/lib-mail/message-send.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-mail/message-send.c	Sun Aug 15 06:40:30 2004 +0300
@@ -7,75 +7,6 @@
 #include "message-send.h"
 #include "message-size.h"
 
-off_t message_send(struct ostream *output, struct istream *input,
-		   const struct message_size *msg_size,
-		   int cr_skipped, uoff_t max_virtual_size, int *last_cr,
-		   int fix_nuls)
-{
-	const unsigned char *msg;
-	size_t i, size;
-	off_t ret;
-	unsigned char add;
-
-	if (last_cr != NULL)
-		*last_cr = -1;
-
-	if (msg_size->physical_size == 0)
-		return 0;
-
-	if (msg_size->physical_size == msg_size->virtual_size && !fix_nuls) {
-		/* no need to kludge with CRs, we can use sendfile() */
-		input = i_stream_create_limit(default_pool, input,
-					      input->v_offset,
-					      max_virtual_size);
-		ret = o_stream_send_istream(output, input);
-		i_stream_unref(input);
-		return ret;
-	}
-
-	/* go through the message data and insert CRs where needed.  */
-	ret = 0;
-	while (max_virtual_size > 0 &&
-	       i_stream_read_data(input, &msg, &size, 0) > 0) {
-		add = '\0';
-		for (i = 0; i < size && max_virtual_size > 0; i++) {
-			max_virtual_size--;
-
-			if (msg[i] == '\n') {
-				if ((i > 0 && msg[i-1] != '\r') ||
-				    (i == 0 && !cr_skipped)) {
-					/* missing CR */
-					add = '\r';
-					break;
-				}
-			} else if (msg[i] == '\0') {
-				add = 128;
-				break;
-			}
-		}
-
-		ret += i;
-		if (o_stream_send(output, msg, i) < 0)
-			return -1;
-
-		if (add != '\0') {
-			ret++;
-			if (o_stream_send(output, &add, 1) < 0)
-				return -1;
-			cr_skipped = add == '\r';
-			if (add == 128) i++;
-		} else {
-			cr_skipped = i > 0 && msg[i-1] == '\r';
-		}
-
-		i_stream_skip(input, i);
-	}
-
-	if (last_cr != NULL)
-		*last_cr = cr_skipped;
-	return ret;
-}
-
 void message_skip_virtual(struct istream *input, uoff_t virtual_skip,
 			  struct message_size *msg_size,
 			  int cr_skipped, int *last_cr)
--- a/src/lib-mail/message-send.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-mail/message-send.h	Sun Aug 15 06:40:30 2004 +0300
@@ -3,15 +3,6 @@
 
 struct message_size;
 
-/* Send message to client inserting CRs if needed. Only max_virtual_size
-   bytes are sent. If cr_skipped is FALSE and input begins with LF, it's
-   treated as CRLF. last_cr is set to 1, 0 or -1 if not known. Returns number
-   of bytes sent, or -1 if error. */
-off_t message_send(struct ostream *output, struct istream *input,
-		   const struct message_size *msg_size,
-		   int cr_skipped, uoff_t max_virtual_size, int *last_cr,
-		   int fix_nuls);
-
 /* Skip number of virtual bytes from putfer. msg_size is updated if it's not
    NULL. If cr_skipped is TRUE and first character is \n, it's not treated as
    \r\n. last_cr is set to TRUE if last character we skipped was \r, meaning
--- a/src/lib-storage/index/index-mail-headers.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/index-mail-headers.c	Sun Aug 15 06:40:30 2004 +0300
@@ -320,10 +320,8 @@
 {
 	struct index_mail_data *data = &mail->data;
 
-	if (data->stream == NULL) {
-		if (mail->mail.get_stream(&mail->mail, NULL, NULL) == NULL)
-			return FALSE;
-	}
+	if (mail->mail.get_stream(&mail->mail, NULL, NULL) == NULL)
+		return FALSE;
 
 	index_mail_parse_header_init(mail, headers);
 
@@ -508,7 +506,7 @@
 }
 
 static void header_cache_callback(struct message_header_line *hdr,
-				  int matched __attr_unused__, void *context)
+				  int *matched __attr_unused__, void *context)
 {
 	struct index_mail *mail = context;
 
@@ -534,10 +532,8 @@
 	/* not in cache / error */
 	p_free(mail->pool, dest);
 
-	if (mail->data.stream == NULL) {
-		if (mail->mail.get_stream(&mail->mail, NULL, NULL) == NULL)
-			return FALSE;
-	}
+	if (mail->mail.get_stream(&mail->mail, NULL, NULL) == NULL)
+		return FALSE;
 
 	if (mail->data.filter_stream != NULL)
 		i_stream_unref(mail->data.filter_stream);
@@ -551,13 +547,6 @@
 	return mail->data.filter_stream;
 }
 
-static int strcasecmp_p(const void *p1, const void *p2)
-{
-	const char *const *s1 = p1, *const *s2 = p2;
-
-	return strcasecmp(*s1, *s2);
-}
-
 struct mailbox_header_lookup_ctx *
 index_header_lookup_init(struct mailbox *box, const char *const headers[])
 {
--- a/src/lib-storage/index/index-mail.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/index-mail.c	Sun Aug 15 06:40:30 2004 +0300
@@ -236,12 +236,12 @@
 	return data->sent_date.time;
 }
 
-static int get_msgpart_sizes(struct index_mail *mail)
+static int get_cached_msgpart_sizes(struct index_mail *mail)
 {
 	struct index_mail_data *data = &mail->data;
 
 	if (data->parts == NULL)
-		(void)index_mail_get_parts(&mail->mail);
+		data->parts = get_cached_parts(mail);
 
 	if (data->parts != NULL) {
 		data->hdr_size = data->parts->header_size;
@@ -268,12 +268,15 @@
 	if (data->size != (uoff_t)-1)
 		return data->size;
 
-	if (get_msgpart_sizes(mail))
+	if (get_cached_msgpart_sizes(mail))
 		return data->size;
 
 	if (_mail->get_stream(_mail, &hdr_size, &body_size) == NULL)
 		return (uoff_t)-1;
 
+	mail_cache_add(mail->trans->cache_trans, mail->data.seq,
+		       cache_fields[MAIL_CACHE_VIRTUAL_FULL_SIZE].idx,
+		       &data->size, sizeof(data->size));
 	return data->size;
 }
 
@@ -373,7 +376,7 @@
 	struct index_mail_data *data = &mail->data;
 
 	if (hdr_size != NULL || body_size != NULL)
-		(void)get_msgpart_sizes(mail);
+		(void)get_cached_msgpart_sizes(mail);
 
 	if (hdr_size != NULL) {
 		if (!data->hdr_size_set) {
--- a/src/lib-storage/index/index-storage.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/index-storage.c	Sun Aug 15 06:40:30 2004 +0300
@@ -223,14 +223,6 @@
 	const char *str;
 	time_t now;
 
-	if ((secs_left % 15) != 0) {
-		/* update alarm() so that we get back here around the same
-		   time we want the next notify. also try to use somewhat
-		   rounded times. this affects only fcntl() locking, dotlock
-		   and flock() calls should be calling us constantly */
-		alarm(secs_left%15);
-	}
-
 	/* if notify type changes, print the message immediately */
 	now = time(NULL);
 	if (ibox->last_notify_type == MAILBOX_LOCK_NOTIFY_NONE ||
--- a/src/lib-storage/index/maildir/maildir-save.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/maildir/maildir-save.c	Sun Aug 15 06:40:30 2004 +0300
@@ -47,8 +47,7 @@
 	i_assert(fname != NULL);
 	fname++;
 
-	output = o_stream_create_file(fd, pool_datastack_create(), 4096, FALSE);
-	o_stream_set_blocking(output, 60000, NULL, NULL);
+	output = o_stream_create_file(fd, pool_datastack_create(), 0, FALSE);
 
 	crlf = getenv("MAIL_SAVE_CRLF") != NULL;
 	if (mail_storage_save(ibox->box.storage, path, input, output,
--- a/src/lib-storage/index/mbox/istream-raw-mbox.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/mbox/istream-raw-mbox.c	Sun Aug 15 06:40:30 2004 +0300
@@ -40,15 +40,6 @@
 	i_stream_set_max_buffer_size(rstream->input, max_size);
 }
 
-static void _set_blocking(struct _iostream *stream, int timeout_msecs,
-			  void (*timeout_cb)(void *), void *context)
-{
-	struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
-
-	i_stream_set_blocking(rstream->input, timeout_msecs,
-			      timeout_cb, context);
-}
-
 static int mbox_read_from_line(struct raw_mbox_istream *rstream)
 {
 	const unsigned char *buf, *p;
@@ -298,7 +289,6 @@
 	rstream->istream.iostream.close = _close;
 	rstream->istream.iostream.destroy = _destroy;
 	rstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	rstream->istream.iostream.set_blocking = _set_blocking;
 
 	rstream->istream.read = _read;
 	rstream->istream.seek = _seek;
--- a/src/lib-storage/index/mbox/mbox-lock.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Sun Aug 15 06:40:30 2004 +0300
@@ -347,6 +347,7 @@
 {
 	struct flock fl;
 	time_t now;
+	unsigned int next_alarm;
 	int wait_type;
 
 	if (mbox_file_open_latest(ctx, lock_type) < 0)
@@ -361,23 +362,40 @@
 	fl.l_start = 0;
 	fl.l_len = 0;
 
-        wait_type = max_wait_time == 0 ? F_SETLK : F_SETLKW;
+	if (max_wait_time == 0)
+		wait_type = F_SETLK;
+	else {
+		wait_type = F_SETLKW;
+		alarm(I_MIN(max_wait_time, 5));
+	}
+
 	while (fcntl(ctx->ibox->mbox_fd, wait_type, &fl) < 0) {
 		if (errno != EINTR) {
 			if (errno != EAGAIN && errno != EACCES)
 				mbox_set_syscall_error(ctx->ibox, "fcntl()");
+			alarm(0);
 			return -1;
 		}
 
 		now = time(NULL);
-		if (max_wait_time != 0 && now >= max_wait_time)
+		if (max_wait_time != 0 && now >= max_wait_time) {
+			alarm(0);
 			return 0;
+		}
+
+		/* notify locks once every 5 seconds.
+		   try to use rounded values. */
+		next_alarm = (max_wait_time - now) % 5;
+		if (next_alarm == 0)
+			next_alarm = 5;
+		alarm(next_alarm);
 
 		index_storage_lock_notify(ctx->ibox,
 					  MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
 					  max_wait_time - now);
 	}
 
+	alarm(0);
 	return 1;
 }
 
--- a/src/lib-storage/index/mbox/mbox-save.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/index/mbox/mbox-save.c	Sun Aug 15 06:40:30 2004 +0300
@@ -292,8 +292,7 @@
 			return -1;
 
 		ctx->output = o_stream_create_file(ibox->mbox_fd, default_pool,
-						   4096, FALSE);
-		o_stream_set_blocking(ctx->output, 60000, NULL, NULL);
+						   0, FALSE);
 	}
 
 	if (!ctx->synced && mail_r != NULL) {
--- a/src/lib-storage/mail-storage.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib-storage/mail-storage.h	Sun Aug 15 06:40:30 2004 +0300
@@ -84,9 +84,17 @@
 };
 
 enum mailbox_sync_flags {
+	/* Normally syncing checks if mailbox has changed, if yes it reads it
+	   but doesn't necessarily write our internal state back to mailbox.
+
+	   Fast syncing doesn't necessarily even check if mailbox has changed.
+
+	   Full syncing makes sure our internal state is fully synced with the
+	   mailbox. */
 	MAILBOX_SYNC_FLAG_FAST		= 0x01,
-	MAILBOX_SYNC_FLAG_NO_EXPUNGES	= 0x02,
-	MAILBOX_SYNC_AUTO_STOP		= 0x04
+	MAILBOX_SYNC_FLAG_FULL		= 0x02,
+	MAILBOX_SYNC_FLAG_NO_EXPUNGES	= 0x04,
+	MAILBOX_SYNC_AUTO_STOP		= 0x08
 };
 
 enum mailbox_sync_type {
--- a/src/lib/Makefile.am	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/Makefile.am	Sun Aug 15 06:40:30 2004 +0300
@@ -1,7 +1,6 @@
 noinst_LIBRARIES = liblib.a
 
 liblib_a_SOURCES = \
-	alarm-hup.c \
 	base64.c \
 	buffer.c \
 	byteorder.c \
@@ -68,7 +67,6 @@
 	write-full.c
 
 noinst_HEADERS = \
-	alarm-hup.h \
 	base64.h \
 	buffer.h \
 	byteorder.h \
--- a/src/lib/alarm-hup.c	Sun Aug 15 05:54:47 2004 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/* Copyright (c) 2002-2003 Timo Sirainen */
-
-#include "lib.h"
-#include "alarm-hup.h"
-
-#include <signal.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-static int initialized = FALSE;
-static unsigned int alarm_timeout = 30;
-
-unsigned int alarm_hup_set_interval(unsigned int timeout)
-{
-	unsigned int old;
-
-	old = alarm_timeout;
-	alarm_timeout = timeout;
-
-	alarm(alarm_timeout);
-	return old;
-}
-
-static void sig_alarm(int signo __attr_unused__)
-{
-	/* do it again */
-	alarm(alarm_timeout);
-}
-
-void alarm_hup_init(void)
-{
-	struct sigaction act;
-
-	if (initialized)
-		return;
-	initialized = TRUE;
-
-	if (sigemptyset(&act.sa_mask) < 0)
-		i_fatal("sigemptyset(): %m");
-	act.sa_flags = 0;
-	act.sa_handler = sig_alarm;
-
-	while (sigaction(SIGALRM, &act, NULL) < 0) {
-		if (errno != EINTR)
-			i_fatal("sigaction(): %m");
-	}
-
-	alarm(alarm_timeout);
-}
-
-void alarm_hup_deinit(void)
-{
-	struct sigaction act;
-
-	if (!initialized)
-		return;
-	initialized = FALSE;
-
-	alarm(0);
-
-	if (sigemptyset(&act.sa_mask) < 0)
-		i_fatal("sigemptyset(): %m");
-	act.sa_flags = 0;
-	act.sa_handler = SIG_DFL;
-	while (sigaction(SIGALRM, &act, NULL) < 0) {
-		if (errno != EINTR)
-			i_fatal("sigaction(): %m");
-	}
-}
--- a/src/lib/alarm-hup.h	Sun Aug 15 05:54:47 2004 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-#ifndef __ALARM_HUP_H
-#define __ALARM_HUP_H
-
-/* Set new alarm() interval. Returns the old one. alarm() is called
-   immediately with the specified timeout. */
-unsigned int alarm_hup_set_interval(unsigned int timeout);
-
-/* init() may be called multiple times. */
-void alarm_hup_init(void);
-void alarm_hup_deinit(void);
-
-#endif
--- a/src/lib/file-lock.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/file-lock.c	Sun Aug 15 06:40:30 2004 +0300
@@ -1,7 +1,6 @@
 /* Copyright (c) 2002-2003 Timo Sirainen */
 
 #include "lib.h"
-#include "alarm-hup.h"
 #include "file-lock.h"
 
 #include <time.h>
@@ -24,13 +23,13 @@
 {
 	struct flock fl;
 	time_t timeout_time, now;
+	unsigned int next_alarm;
 
 	if (timeout == 0)
 		timeout_time = 0;
 	else {
-		alarm_hup_init();
 		timeout_time = time(NULL) + timeout;
-		alarm(timeout);
+		alarm(I_MIN(timeout, 5));
 	}
 
 	fl.l_type = lock_type;
@@ -39,21 +38,32 @@
 	fl.l_len = 0;
 
 	while (fcntl(fd, timeout != 0 ? F_SETLKW : F_SETLK, &fl) < 0) {
-		if (timeout == 0 && (errno == EACCES || errno == EAGAIN))
+		if (timeout == 0 && (errno == EACCES || errno == EAGAIN)) {
+			alarm(0);
 			return 0;
+		}
 
-		if (errno != EINTR)
+		if (errno != EINTR) {
+			alarm(0);
 			return -1;
+		}
 
 		now = time(NULL);
 		if (timeout != 0 && now >= timeout_time) {
 			errno = EAGAIN;
+			alarm(0);
 			return 0;
 		}
 
+		next_alarm = (timeout_time - now) % 5;
+		if (next_alarm == 0)
+			next_alarm = 5;
+		alarm(next_alarm);
+
 		if (callback != NULL)
 			callback(timeout_time - now, context);
 	}
 
+	alarm(0);
 	return 1;
 }
--- a/src/lib/iostream-internal.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/iostream-internal.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,7 +1,7 @@
 #ifndef __IOSTREAM_INTERNAL_H
 #define __IOSTREAM_INTERNAL_H
 
-/* This file is private to IStream and OStream implementation */
+/* This file is private to input stream and output stream implementations */
 
 struct _iostream {
 	pool_t pool;
@@ -10,8 +10,6 @@
 	void (*close)(struct _iostream *stream);
 	void (*destroy)(struct _iostream *stream);
 	void (*set_max_buffer_size)(struct _iostream *stream, size_t max_size);
-	void (*set_blocking)(struct _iostream *stream, int timeout_msecs,
-			     void (*timeout_cb)(void *), void *context);
 };
 
 void _io_stream_init(pool_t pool, struct _iostream *stream);
@@ -19,13 +17,5 @@
 void _io_stream_unref(struct _iostream *stream);
 void _io_stream_close(struct _iostream *stream);
 void _io_stream_set_max_buffer_size(struct _iostream *stream, size_t max_size);
-void _io_stream_set_blocking(struct _iostream *stream, int timeout_msecs,
-			     void (*timeout_cb)(void *), void *context);
-
-#define GET_TIMEOUT_TIME(fstream) \
-        ((fstream)->timeout_msecs <= 0 ? 0 : \
-	 time(NULL) + ((fstream)->timeout_msecs / 1000))
-#define STREAM_IS_BLOCKING(fstream) \
-	((fstream)->timeout_msecs != 0)
 
 #endif
--- a/src/lib/iostream.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/iostream.c	Sun Aug 15 06:40:30 2004 +0300
@@ -40,9 +40,3 @@
 {
 	stream->set_max_buffer_size(stream, max_size);
 }
-
-void _io_stream_set_blocking(struct _iostream *stream, int timeout_msecs,
-			     void (*timeout_cb)(void *), void *context)
-{
-	stream->set_blocking(stream, timeout_msecs, timeout_cb, context);
-}
--- a/src/lib/istream-data.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream-data.c	Sun Aug 15 06:40:30 2004 +0300
@@ -16,13 +16,6 @@
 {
 }
 
-static void _set_blocking(struct _iostream *stream __attr_unused__,
-			  int timeout_msecs __attr_unused__,
-			  void (*timeout_cb)(void *) __attr_unused__,
-			  void *context __attr_unused__)
-{
-}
-
 static ssize_t _read(struct _istream *stream __attr_unused__)
 {
 	return -1;
@@ -51,7 +44,6 @@
 	stream->iostream.close = _close;
 	stream->iostream.destroy = _destroy;
 	stream->iostream.set_max_buffer_size = _set_max_buffer_size;
-	stream->iostream.set_blocking = _set_blocking;
 
 	stream->read = _read;
 	stream->seek = _seek;
--- a/src/lib/istream-file.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream-file.c	Sun Aug 15 06:40:30 2004 +0300
@@ -3,7 +3,6 @@
 /* @UNSAFE: whole file */
 
 #include "lib.h"
-#include "alarm-hup.h"
 #include "istream-internal.h"
 #include "network.h"
 
@@ -13,19 +12,12 @@
 
 #define I_STREAM_MIN_SIZE 4096
 
-#define STREAM_IS_BLOCKING(fstream) \
-	((fstream)->timeout_msecs != 0)
-
 struct file_istream {
 	struct _istream istream;
 
 	size_t max_buffer_size;
 	uoff_t skip_left;
 
-	int timeout_msecs;
-	void (*timeout_cb)(void *);
-	void *timeout_context;
-
 	unsigned int file:1;
 	unsigned int autoclose_fd:1;
 };
@@ -56,21 +48,6 @@
 	fstream->max_buffer_size = max_size;
 }
 
-static void _set_blocking(struct _iostream *stream, int timeout_msecs,
-			  void (*timeout_cb)(void *), void *context)
-{
-	struct file_istream *fstream = (struct file_istream *) stream;
-
-	fstream->timeout_msecs = timeout_msecs;
-	fstream->timeout_cb = timeout_cb;
-	fstream->timeout_context = context;
-
-	net_set_nonblock(fstream->istream.fd, timeout_msecs == 0);
-
-	if (timeout_msecs != 0)
-		alarm_hup_init();
-}
-
 static void i_stream_grow_buffer(struct _istream *stream, size_t bytes)
 {
 	struct file_istream *fstream = (struct file_istream *) stream;
@@ -105,7 +82,6 @@
 static ssize_t _read(struct _istream *stream)
 {
 	struct file_istream *fstream = (struct file_istream *) stream;
-	time_t timeout_time;
 	size_t size;
 	ssize_t ret;
 
@@ -129,64 +105,52 @@
 	}
 
 	size = stream->buffer_size - stream->pos;
-	timeout_time = GET_TIMEOUT_TIME(fstream);
 
 	ret = -1;
-	do {
-		if (ret == 0 && timeout_time > 0 && time(NULL) > timeout_time) {
-			/* timeouted */
-			if (fstream->timeout_cb != NULL)
-				fstream->timeout_cb(fstream->timeout_context);
-			stream->istream.stream_errno = EAGAIN;
-			return -1;
-		}
 
-		if (fstream->file) {
-			ret = pread(stream->fd,
-				    stream->w_buffer + stream->pos, size,
-				    stream->istream.v_offset +
-				    (stream->pos - stream->skip));
-		} else {
-			ret = read(stream->fd,
-				   stream->w_buffer + stream->pos, size);
-		}
-		if (ret == 0) {
-			/* EOF */
-			if (!fstream->file)
-				stream->istream.disconnected = TRUE;
+	if (fstream->file) {
+		ret = pread(stream->fd, stream->w_buffer + stream->pos, size,
+			    stream->istream.v_offset +
+			    (stream->pos - stream->skip));
+	} else {
+		ret = read(stream->fd, stream->w_buffer + stream->pos, size);
+	}
+	if (ret == 0) {
+		/* EOF */
+		if (!fstream->file)
+			stream->istream.disconnected = TRUE;
+		return -1;
+	}
+
+	if (ret < 0) {
+		if (errno == ECONNRESET || errno == ETIMEDOUT) {
+			/* treat as disconnection */
+			stream->istream.disconnected = TRUE;
 			return -1;
 		}
 
-		if (ret < 0) {
-			if (errno == ECONNRESET || errno == ETIMEDOUT) {
-				/* treat as disconnection */
-				stream->istream.disconnected = TRUE;
-				return -1;
-			}
-
-			if (errno == EINTR || errno == EAGAIN)
-				ret = 0;
-			else {
-				stream->istream.stream_errno = errno;
-				return -1;
-			}
+		if (errno == EINTR || errno == EAGAIN)
+			ret = 0;
+		else {
+			stream->istream.stream_errno = errno;
+			return -1;
 		}
+	}
 
-		if (ret > 0 && fstream->skip_left > 0) {
-			i_assert(!fstream->file);
-			i_assert(stream->skip == stream->pos);
+	if (ret > 0 && fstream->skip_left > 0) {
+		i_assert(!fstream->file);
+		i_assert(stream->skip == stream->pos);
 
-			if (fstream->skip_left >= (size_t)ret) {
-				fstream->skip_left -= ret;
-				ret = 0;
-			} else {
-				ret -= fstream->skip_left;
-				stream->pos += fstream->skip_left;
-				stream->skip += fstream->skip_left;
-				fstream->skip_left = 0;
-			}
+		if (fstream->skip_left >= (size_t)ret) {
+			fstream->skip_left -= ret;
+			ret = 0;
+		} else {
+			ret -= fstream->skip_left;
+			stream->pos += fstream->skip_left;
+			stream->skip += fstream->skip_left;
+			fstream->skip_left = 0;
 		}
-	} while (ret == 0 && STREAM_IS_BLOCKING(fstream));
+	}
 
 	stream->pos += ret;
 	return ret;
@@ -233,7 +197,6 @@
 	fstream->istream.iostream.close = _close;
 	fstream->istream.iostream.destroy = _destroy;
 	fstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	fstream->istream.iostream.set_blocking = _set_blocking;
 
 	fstream->istream.read = _read;
 	fstream->istream.seek = _seek;
--- a/src/lib/istream-limit.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream-limit.c	Sun Aug 15 06:40:30 2004 +0300
@@ -31,15 +31,6 @@
 	i_stream_set_max_buffer_size(lstream->input, max_size);
 }
 
-static void _set_blocking(struct _iostream *stream, int timeout_msecs,
-			  void (*timeout_cb)(void *), void *context)
-{
-	struct limit_istream *lstream = (struct limit_istream *) stream;
-
-	i_stream_set_blocking(lstream->input, timeout_msecs,
-			      timeout_cb, context);
-}
-
 static ssize_t _read(struct _istream *stream)
 {
 	struct limit_istream *lstream = (struct limit_istream *) stream;
@@ -118,7 +109,6 @@
 	lstream->istream.iostream.close = _close;
 	lstream->istream.iostream.destroy = _destroy;
 	lstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	lstream->istream.iostream.set_blocking = _set_blocking;
 
 	lstream->istream.read = _read;
 	lstream->istream.seek = _seek;
--- a/src/lib/istream-mmap.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream-mmap.c	Sun Aug 15 06:40:30 2004 +0300
@@ -68,14 +68,6 @@
 	}
 }
 
-static void _set_blocking(struct _iostream *stream __attr_unused__,
-			  int timeout_msecs __attr_unused__,
-			  void (*timeout_cb)(void *) __attr_unused__,
-			  void *context __attr_unused__)
-{
-	/* we never block */
-}
-
 static ssize_t _read(struct _istream *stream)
 {
 	struct mmap_istream *mstream = (struct mmap_istream *) stream;
@@ -140,6 +132,7 @@
 	}
 
 	stream->pos = stream->buffer_size;
+	i_assert(stream->pos - stream->skip != 0);
 	return stream->pos - stream->skip;
 }
 
@@ -201,7 +194,6 @@
 	mstream->istream.iostream.close = _close;
 	mstream->istream.iostream.destroy = _destroy;
 	mstream->istream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	mstream->istream.iostream.set_blocking = _set_blocking;
 
 	mstream->istream.read = _read;
 	mstream->istream.seek = _seek;
--- a/src/lib/istream.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream.c	Sun Aug 15 06:40:30 2004 +0300
@@ -32,13 +32,6 @@
 				       max_size);
 }
 
-void i_stream_set_blocking(struct istream *stream, int timeout_msecs,
-			   void (*timeout_cb)(void *), void *context)
-{
-	_io_stream_set_blocking(&stream->real_stream->iostream, timeout_msecs,
-				timeout_cb, context);
-}
-
 ssize_t i_stream_read(struct istream *stream)
 {
 	struct _istream *_stream = stream->real_stream;
--- a/src/lib/istream.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/istream.h	Sun Aug 15 06:40:30 2004 +0300
@@ -37,12 +37,6 @@
 /* Change the maximum size for stream's input buffer to grow. Useful only
    for buffered streams (currently only file). */
 void i_stream_set_max_buffer_size(struct istream *stream, size_t max_size);
-/* Makes reads blocking until at least one byte is read. timeout_cb is
-   called if nothing is read in specified time. Setting timeout_msecs to 0
-   makes it non-blocking. This call changes non-blocking state of file
-   descriptor. */
-void i_stream_set_blocking(struct istream *stream, int timeout_msecs,
-			   void (*timeout_cb)(void *), void *context);
 
 /* Returns number of bytes read if read was ok, -1 if EOF or error, -2 if the
    input buffer is full. */
--- a/src/lib/lib.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/lib.c	Sun Aug 15 06:40:30 2004 +0300
@@ -1,7 +1,6 @@
 /* Copyright (c) 2001-2003 Timo Sirainen */
 
 #include "lib.h"
-#include "alarm-hup.h"
 #include "hostpid.h"
 
 #include <stdlib.h>
@@ -29,8 +28,6 @@
 
 void lib_deinit(void)
 {
-	alarm_hup_deinit(); /* doesn't harm even if init is never called */
-
         imem_deinit();
 	data_stack_deinit();
         failures_deinit();
--- a/src/lib/ostream-file.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/ostream-file.c	Sun Aug 15 06:40:30 2004 +0300
@@ -3,7 +3,6 @@
 /* @UNSAFE: whole file */
 
 #include "lib.h"
-#include "alarm-hup.h"
 #include "ioloop.h"
 #include "write-full.h"
 #include "network.h"
@@ -39,10 +38,6 @@
 	size_t buffer_size, max_buffer_size, optimal_block_size;
 	size_t head, tail; /* first unsent/unused byte */
 
-	int timeout_msecs;
-	void (*timeout_cb)(void *);
-	void *timeout_context;
-
 	unsigned int full:1; /* if head == tail, is buffer empty or full? */
 	unsigned int file:1;
 	unsigned int corked:1;
@@ -51,6 +46,8 @@
 	unsigned int autoclose_fd:1;
 };
 
+static void stream_send_io(void *context);
+
 static void stream_closed(struct file_ostream *fstream)
 {
 	if (fstream->autoclose_fd && fstream->fd != -1) {
@@ -69,7 +66,7 @@
 
 static void _close(struct _iostream *stream)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
 
 	/* flush output before really closing it */
 	o_stream_flush(&fstream->ostream.ostream);
@@ -79,65 +76,18 @@
 
 static void _destroy(struct _iostream *stream)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
 
 	p_free(fstream->ostream.iostream.pool, fstream->buffer);
 }
 
 static void _set_max_buffer_size(struct _iostream *stream, size_t max_size)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
 
 	fstream->max_buffer_size = max_size;
 }
 
-static void _set_blocking(struct _iostream *stream, int timeout_msecs,
-			  void (*timeout_cb)(void *), void *context)
-{
-	struct file_ostream *fstream = (struct file_ostream *) stream;
-
-	fstream->timeout_msecs = timeout_msecs;
-	fstream->timeout_cb = timeout_cb;
-	fstream->timeout_context = context;
-
-	if (!fstream->file)
-		net_set_nonblock(fstream->fd, timeout_msecs == 0);
-
-	if (timeout_msecs != 0)
-		alarm_hup_init();
-}
-
-static void _cork(struct _ostream *stream)
-{
-	struct file_ostream *fstream = (struct file_ostream *) stream;
-
-	if (!fstream->corked) {
-		if (!fstream->no_socket_cork) {
-			if (net_set_cork(fstream->fd, TRUE) < 0)
-				fstream->no_socket_cork = TRUE;
-		}
-		fstream->corked = TRUE;
-	}
-}
-
-static void update_iovec(struct iovec *iov, unsigned int iov_size, size_t size)
-{
-	while (size > 0) {
-		i_assert(iov_size > 0);
-
-		if ((size_t)iov->iov_len <= size) {
-			size -= iov->iov_len;
-			iov->iov_base = NULL;
-			iov->iov_len = 0;
-		} else {
-			iov->iov_base = (void *)((char *)iov->iov_base + size);
-			iov->iov_len -= size;
-			size = 0;
-		}
-		iov++; iov_size--;
-	}
-}
-
 static void update_buffer(struct file_ostream *fstream, size_t size)
 {
 	size_t used;
@@ -175,22 +125,15 @@
 }
 
 /* NOTE: modifies iov */
-static ssize_t
-o_stream_writev(struct file_ostream *fstream, struct iovec *iov, int iov_size)
+static ssize_t o_stream_writev(struct file_ostream *fstream,
+			       const struct const_iovec *iov, int iov_size)
 {
 	ssize_t ret;
 
-	while (iov->iov_len == 0 && iov_size > 0) {
-		iov++;
-		iov_size--;
-	}
-
-	i_assert(iov_size > 0);
-
 	if (iov_size == 1)
 		ret = write(fstream->fd, iov->iov_base, iov->iov_len);
 	else
-		ret = writev(fstream->fd, iov, iov_size);
+		ret = writev(fstream->fd, (const struct iovec *)iov, iov_size);
 
 	if (ret < 0) {
 		if (errno == EAGAIN || errno == EINTR)
@@ -200,107 +143,81 @@
 		return -1;
 	}
 
-	update_iovec(iov, iov_size, ret);
-	update_buffer(fstream, ret);
-
 	return ret;
 }
 
 /* returns how much of vector was used */
 static int o_stream_fill_iovec(struct file_ostream *fstream,
-			       struct iovec iov[2])
+			       struct const_iovec iov[2])
 {
 	if (IS_STREAM_EMPTY(fstream))
 		return 0;
 
 	if (fstream->head < fstream->tail) {
-		iov[0].iov_base = (void *)(fstream->buffer + fstream->head);
+		iov[0].iov_base = fstream->buffer + fstream->head;
 		iov[0].iov_len = fstream->tail - fstream->head;
 		return 1;
 	} else {
-		iov[0].iov_base = (void *)(fstream->buffer + fstream->head);
+		iov[0].iov_base = fstream->buffer + fstream->head;
 		iov[0].iov_len = fstream->buffer_size - fstream->head;
 		if (fstream->tail == 0)
 			return 1;
 		else {
-			iov[1].iov_base = (void *)fstream->buffer;
+			iov[1].iov_base = fstream->buffer;
 			iov[1].iov_len = fstream->tail;
 			return 2;
 		}
 	}
 }
 
-static int o_stream_send_blocking(struct file_ostream *fstream,
-				  const void *data, size_t size)
+static int buffer_flush(struct file_ostream *fstream)
 {
-	time_t timeout_time;
-	struct iovec iov[3];
-	int iov_len, first;
+	struct const_iovec iov[2];
+	int iov_len;
+	ssize_t ret;
 
 	iov_len = o_stream_fill_iovec(fstream, iov);
-	if (size > 0) {
-		iov[iov_len].iov_base = (void *) data;
-		iov[iov_len].iov_len = size;
-		iov_len++;
+	if (iov_len > 0) {
+		ret = o_stream_writev(fstream, iov, iov_len);
+		if (ret < 0)
+			return -1;
+
+		update_buffer(fstream, ret);
 	}
 
-	first = TRUE;
-
-	timeout_time = GET_TIMEOUT_TIME(fstream);
-	while (iov[iov_len-1].iov_len != 0) {
-		if (first)
-			first = FALSE;
-		else if (timeout_time > 0 && time(NULL) > timeout_time) {
-			/* timeouted */
-			if (fstream->timeout_cb != NULL)
-				fstream->timeout_cb(fstream->timeout_context);
-			fstream->ostream.ostream.stream_errno = EAGAIN;
-			return -1;
-		}
-
-		if (o_stream_writev(fstream, iov, iov_len) < 0)
-			return -1;
-	}
-
-        return 1;
+	return IS_STREAM_EMPTY(fstream) ? 1 : 0;
 }
 
-static int buffer_flush(struct file_ostream *fstream)
+static void _cork(struct _ostream *stream, int set)
 {
-	struct iovec iov[2];
-	int iov_len;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
+
+	if (fstream->corked != set) {
+		if (!fstream->no_socket_cork) {
+			if (net_set_cork(fstream->fd, set) < 0)
+				fstream->no_socket_cork = TRUE;
+		}
+		fstream->corked = set;
 
-	if (!IS_STREAM_EMPTY(fstream)) {
-		iov_len = o_stream_fill_iovec(fstream, iov);
-		if (o_stream_writev(fstream, iov, iov_len) < 0)
-			return -1;
-
-		if (!IS_STREAM_EMPTY(fstream)) {
-			if (o_stream_send_blocking(fstream, NULL, 0) < 0)
-				return -1;
+		if (set && fstream->io != NULL) {
+			io_remove(fstream->io);
+			fstream->io = NULL;
+		} else if (!set && fstream->io == NULL) {
+			if (fstream->file)
+				buffer_flush(fstream);
+			else {
+				fstream->io = io_add(fstream->fd, IO_WRITE,
+						     stream_send_io, fstream);
+			}
 		}
 	}
-
-	return 1;
 }
 
 static int _flush(struct _ostream *stream)
 {
 	struct file_ostream *fstream = (struct file_ostream *) stream;
-	int ret;
 
-	ret = buffer_flush(fstream);
-
-	if (fstream->corked) {
-		/* remove cork */
-		if (!fstream->no_socket_cork) {
-			if (net_set_cork(fstream->fd, FALSE) < 0)
-				i_error("net_set_cork() failed: %m");
-		}
-		fstream->corked = FALSE;
-	}
-
-	return ret;
+	return buffer_flush(fstream);
 }
 
 static size_t get_unused_space(struct file_ostream *fstream)
@@ -317,25 +234,16 @@
 	}
 }
 
-static int _have_space(struct _ostream *stream, size_t size)
+static size_t _get_used_size(struct _ostream *stream)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
-	size_t unused;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
 
-	unused = get_unused_space(fstream);
-	if (size <= unused)
-		return 1;
-
-	if (fstream->max_buffer_size == 0)
-		return 1;
-
-	unused += (fstream->max_buffer_size - fstream->buffer_size);
-	return size <= unused ? 1 : 0;
+	return fstream->buffer_size - get_unused_space(fstream);
 }
 
 static int _seek(struct _ostream *stream, uoff_t offset)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
 	off_t ret;
 
 	if (offset > OFF_T_MAX) {
@@ -367,17 +275,16 @@
 	size_t size, head_size;
 
 	size = nearest_power(fstream->buffer_size + bytes);
-	if (fstream->max_buffer_size != 0) {
-		if (size > fstream->max_buffer_size) {
-			/* limit the size */
-			size = fstream->max_buffer_size;
-		} else if (fstream->corked) {
-			/* use the largest possible buffer with corking */
-			size = fstream->max_buffer_size;
-		}
+	if (size > fstream->max_buffer_size) {
+		/* limit the size */
+		size = fstream->max_buffer_size;
+	} else if (fstream->corked) {
+		/* use optimal buffer size with corking */
+		size = I_MIN(fstream->optimal_block_size,
+			     fstream->max_buffer_size);
 	}
 
-	if (size == fstream->buffer_size)
+	if (size <= fstream->buffer_size)
 		return;
 
 	fstream->buffer = p_realloc(fstream->ostream.iostream.pool,
@@ -405,18 +312,18 @@
 static void stream_send_io(void *context)
 {
 	struct file_ostream *fstream = context;
-	struct iovec iov[2];
-	int iov_len;
-
-	iov_len = o_stream_fill_iovec(fstream, iov);
 
-	if (iov_len == 0 || o_stream_writev(fstream, iov, iov_len) < 0 ||
-	    iov[iov_len-1].iov_len == 0) {
-		/* error / all sent */
-		if (fstream->io != NULL) {
-			io_remove(fstream->io);
-			fstream->io = NULL;
-		}
+	if (fstream->ostream.callback != NULL)
+		fstream->ostream.callback(fstream->ostream.context);
+	else {
+		if (_flush(&fstream->ostream) <= 0)
+			return;
+	}
+
+	if (IS_STREAM_EMPTY(fstream) && fstream->io != NULL) {
+		/* all sent */
+		io_remove(fstream->io);
+		fstream->io = NULL;
 	}
 }
 
@@ -455,48 +362,64 @@
 				     fstream);
 	}
 
-	i_assert(!STREAM_IS_BLOCKING(fstream) || sent == size);
 	return sent;
 }
 
-static ssize_t _send(struct _ostream *stream, const void *data, size_t size)
+static ssize_t _sendv(struct _ostream *stream, const struct const_iovec *iov,
+		      size_t iov_count)
 {
-	struct file_ostream *fstream = (struct file_ostream *) stream;
-	struct iovec iov;
+	struct file_ostream *fstream = (struct file_ostream *)stream;
+	size_t i, size, added, optimal_size;
 	ssize_t ret = 0;
 
-	i_assert(size <= SSIZE_T_MAX);
-
 	stream->ostream.stream_errno = 0;
 
-	/* never try sending immediately if fd is blocking,
-	   so we don't need to deal with timeout issues here */
-	if (IS_STREAM_EMPTY(fstream) && !STREAM_IS_BLOCKING(fstream) &&
-	    (!fstream->corked || !_have_space(stream, size))) {
-		iov.iov_base = (void *) data;
-		iov.iov_len = size;
+	for (i = 0, size = 0; i < iov_count; i++)
+		size += iov[i].iov_len;
 
-		ret = o_stream_writev(fstream, &iov, 1);
-		if (ret > 0)
-			stream->ostream.offset += ret;
-
-		if (ret < 0 || (size_t)ret == size)
-			return ret;
-
-		data = (const char *) data + ret;
-		size -= ret;
+	if (size > get_unused_space(fstream)) {
+		if (_flush(stream) < 0)
+			return -1;
 	}
 
-	if (!_have_space(stream, size) && STREAM_IS_BLOCKING(fstream)) {
-		/* send it blocking */
-		if (o_stream_send_blocking(fstream, data, size) < 0)
+	optimal_size = I_MIN(fstream->optimal_block_size,
+			     fstream->max_buffer_size);
+	if (IS_STREAM_EMPTY(fstream) &&
+	    (!fstream->corked || size >= optimal_size)) {
+		/* send immediately */
+		ret = o_stream_writev(fstream, iov, iov_count);
+		if (ret < 0)
 			return -1;
-		ret += (ssize_t)size;
-	} else {
-		/* buffer it, at least partly */
-		ret += (ssize_t)o_stream_add(fstream, data, size);
+
+		size = ret;
+		while (size > 0 && size >= iov[0].iov_len) {
+			size -= iov[0].iov_len;
+			iov++;
+			iov_count--;
+		}
+
+		if (iov_count > 0) {
+			added = o_stream_add(fstream,
+					CONST_PTR_OFFSET(iov[0].iov_base, size),
+					iov[0].iov_len - size);
+			if (added != iov[0].iov_len - size) {
+				/* buffer full */
+				stream->ostream.offset += added;
+				return added;
+			}
+
+			iov++;
+			iov_count--;
+		}
 	}
 
+	/* buffer it, at least partly */
+	for (i = 0; i < iov_count; i++) {
+		added = o_stream_add(fstream, iov[i].iov_base, iov[i].iov_len);
+		ret += added;
+		if (added != iov[i].iov_len)
+			break;
+	}
 	stream->ostream.offset += ret;
 	return ret;
 }
@@ -505,64 +428,35 @@
 				struct istream *instream,
 				int in_fd, uoff_t in_size)
 {
-	struct file_ostream *foutstream = (struct file_ostream *) outstream;
-	time_t timeout_time;
+	struct file_ostream *foutstream = (struct file_ostream *)outstream;
 	uoff_t start_offset;
 	uoff_t offset, send_size, v_offset;
 	ssize_t ret;
-	int first;
-
-	/* set timeout time before hflushing existing buffer which may block */
-	timeout_time = GET_TIMEOUT_TIME(foutstream);
-        start_offset = instream->v_offset;
 
 	/* flush out any data in buffer */
-	if (buffer_flush(foutstream) < 0)
-		return -1;
+	if ((ret = buffer_flush(foutstream)) <= 0)
+		return ret;
 
-	v_offset = instream->v_offset;
-
-	first = TRUE;
+        start_offset = v_offset = instream->v_offset;
 	do {
-		if (first)
-			first = FALSE;
-		else if (timeout_time > 0 && time(NULL) > timeout_time) {
-			/* timeouted */
-			if (foutstream->timeout_cb != NULL) {
-				foutstream->timeout_cb(
-					foutstream->timeout_context);
-			}
-			outstream->ostream.stream_errno = EAGAIN;
-			ret = -1;
-			break;
-		}
-
 		offset = instream->real_stream->abs_start_offset + v_offset;
 		send_size = in_size - v_offset;
 
 		ret = safe_sendfile(foutstream->fd, in_fd, &offset,
 				    MAX_SSIZE_T(send_size));
 		if (ret <= 0) {
-			if (ret == 0) {
-				/* EOF */
+			if (ret == 0 || errno == EINTR || errno == EAGAIN) {
+				ret = 0;
 				break;
 			}
 
-			if (errno != EINTR && errno != EAGAIN) {
-				outstream->ostream.stream_errno = errno;
-				if (errno != EINVAL) {
-					/* close only if error wasn't because
-					   sendfile() isn't supported */
-					stream_closed(foutstream);
-				}
-				break;
+			outstream->ostream.stream_errno = errno;
+			if (errno != EINVAL) {
+				/* close only if error wasn't because
+				   sendfile() isn't supported */
+				stream_closed(foutstream);
 			}
-
-			ret = 0;
-			if (!STREAM_IS_BLOCKING(foutstream)) {
-				/* don't block */
-				break;
-			}
+			break;
 		}
 
 		v_offset += ret;
@@ -576,17 +470,15 @@
 static off_t io_stream_copy(struct _ostream *outstream,
 			    struct istream *instream, uoff_t in_size)
 {
-	struct file_ostream *foutstream = (struct file_ostream *) outstream;
-	time_t timeout_time;
+	struct file_ostream *foutstream = (struct file_ostream *)outstream;
 	uoff_t start_offset;
-	struct iovec iov[3];
+	struct const_iovec iov[3];
 	int iov_len;
 	const unsigned char *data;
 	size_t size, skip_size, block_size;
 	ssize_t ret;
 	int pos;
 
-	timeout_time = GET_TIMEOUT_TIME(foutstream);
 	iov_len = o_stream_fill_iovec(foutstream, iov);
 
         skip_size = 0;
@@ -609,17 +501,12 @@
 		iov[pos].iov_len = size;
 
 		ret = o_stream_writev(foutstream, iov, iov_len);
- 		if (ret < 0) {
-			/* error */
+		if (ret < 0)
 			return -1;
-		}
-
-		if (ret == 0 && !STREAM_IS_BLOCKING(foutstream)) {
-			/* don't block */
-			break;
-		}
 
 		if (skip_size > 0) {
+			update_buffer(foutstream, ret);
+
 			if ((size_t)ret < skip_size) {
 				skip_size -= ret;
 				ret = 0;
@@ -629,27 +516,12 @@
 			}
 		}
 		outstream->ostream.offset += ret;
-
-		if (timeout_time > 0 && time(NULL) > timeout_time) {
-			/* timeouted */
-			if (foutstream->timeout_cb != NULL) {
-				foutstream->timeout_cb(
-					foutstream->timeout_context);
-			}
-			outstream->ostream.stream_errno = EAGAIN;
-			return -1;
-		}
+		i_stream_skip(instream, ret);
 
-		i_stream_skip(instream, size - iov[pos].iov_len);
-		iov_len--;
+		if ((size_t)ret != iov[pos].iov_len)
+			break;
 
-		/* if we already sent the iov[0] and iov[1], we
-		   can just remove them from future calls */
-		while (iov_len > 0 && iov[0].iov_len == 0) {
-			iov[0] = iov[1];
-			if (iov_len > 1) iov[1] = iov[2];
-			iov_len--;
-		}
+		iov_len = 0;
 	}
 
 	return (off_t) (instream->v_offset - start_offset);
@@ -658,8 +530,7 @@
 static off_t io_stream_copy_backwards(struct _ostream *outstream,
 				      struct istream *instream, uoff_t in_size)
 {
-	struct file_ostream *foutstream = (struct file_ostream *) outstream;
-	time_t timeout_time;
+	struct file_ostream *foutstream = (struct file_ostream *)outstream;
 	uoff_t in_start_offset, in_offset, in_limit, out_offset;
 	const unsigned char *data;
 	size_t buffer_size, size, read_size;
@@ -667,8 +538,6 @@
 
 	i_assert(IS_STREAM_EMPTY(foutstream));
 
-	timeout_time = GET_TIMEOUT_TIME(foutstream);
-
 	/* figure out optimal buffer size */
 	buffer_size = instream->real_stream->buffer_size;
 	if (buffer_size == 0 || buffer_size > foutstream->buffer_size) {
@@ -705,7 +574,7 @@
 				size = read_size;
 				if (instream->mmaped) {
 					/* we'll have to write it through
-					   buffer of the file gets corrupted */
+					   buffer or the file gets corrupted */
 					i_assert(size <=
 						 foutstream->buffer_size);
 					memcpy(foutstream->buffer, data, size);
@@ -726,21 +595,11 @@
 			return -1;
 
 		ret = write_full(foutstream->fd, data, size);
- 		if (ret < 0) {
+		if (ret < 0) {
 			/* error */
 			outstream->ostream.stream_errno = errno;
 			return -1;
 		}
-
-		if (timeout_time > 0 && time(NULL) > timeout_time) {
-			/* timeouted */
-			if (foutstream->timeout_cb != NULL) {
-				foutstream->timeout_cb(
-					foutstream->timeout_context);
-			}
-			outstream->ostream.stream_errno = EAGAIN;
-			return -1;
-		}
 	}
 
 	return (off_t) (in_size - in_start_offset);
@@ -748,7 +607,7 @@
 
 static off_t _send_istream(struct _ostream *outstream, struct istream *instream)
 {
-	struct file_ostream *foutstream = (struct file_ostream *) outstream;
+	struct file_ostream *foutstream = (struct file_ostream *)outstream;
 	uoff_t in_size;
 	off_t ret;
 	int in_fd, overlapping;
@@ -814,13 +673,12 @@
 	fstream->ostream.iostream.close = _close;
 	fstream->ostream.iostream.destroy = _destroy;
 	fstream->ostream.iostream.set_max_buffer_size = _set_max_buffer_size;
-	fstream->ostream.iostream.set_blocking = _set_blocking;
 
 	fstream->ostream.cork = _cork;
 	fstream->ostream.flush = _flush;
-	fstream->ostream.have_space = _have_space;
+	fstream->ostream.get_used_size = _get_used_size;
 	fstream->ostream.seek = _seek;
-	fstream->ostream.send = _send;
+	fstream->ostream.sendv = _sendv;
 	fstream->ostream.send_istream = _send_istream;
 
 	ostream = _o_stream_create(&fstream->ostream, pool);
@@ -842,8 +700,6 @@
 			if (S_ISREG(st.st_mode)) {
 				fstream->no_socket_cork = TRUE;
 				fstream->file = TRUE;
-
-				o_stream_set_blocking(ostream, -1, 0, NULL);
 			}
 		}
 #ifndef HAVE_LINUX_SENDFILE
--- a/src/lib/ostream-internal.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/ostream-internal.h	Sun Aug 15 06:40:30 2004 +0300
@@ -9,16 +9,20 @@
 	struct _iostream iostream;
 
 /* methods: */
-	void (*cork)(struct _ostream *stream);
+	void (*cork)(struct _ostream *stream, int set);
 	int (*flush)(struct _ostream *stream);
-	int (*have_space)(struct _ostream *stream, size_t size);
+	size_t (*get_used_size)(struct _ostream *stream);
 	int (*seek)(struct _ostream *stream, uoff_t offset);
-	ssize_t (*send)(struct _ostream *stream, const void *data, size_t size);
+	ssize_t (*sendv)(struct _ostream *stream, const struct const_iovec *iov,
+			 size_t iov_count);
 	off_t (*send_istream)(struct _ostream *outstream,
 			      struct istream *instream);
 
 /* data: */
 	struct ostream ostream;
+
+	io_callback_t *callback;
+	void *context;
 };
 
 struct ostream *_o_stream_create(struct _ostream *_stream, pool_t pool);
--- a/src/lib/ostream.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/ostream.c	Sun Aug 15 06:40:30 2004 +0300
@@ -20,19 +20,21 @@
 	stream->closed = TRUE;
 }
 
+void o_stream_set_flush_callback(struct ostream *stream,
+				 io_callback_t *callback, void *context)
+{
+	struct _ostream *_stream = stream->real_stream;
+
+	_stream->callback = callback;
+	_stream->context = context;
+}
+
 void o_stream_set_max_buffer_size(struct ostream *stream, size_t max_size)
 {
 	_io_stream_set_max_buffer_size(&stream->real_stream->iostream,
 				       max_size);
 }
 
-void o_stream_set_blocking(struct ostream *stream, int timeout_msecs,
-			   void (*timeout_cb)(void *), void *context)
-{
-	_io_stream_set_blocking(&stream->real_stream->iostream, timeout_msecs,
-				timeout_cb, context);
-}
-
 void o_stream_cork(struct ostream *stream)
 {
 	struct _ostream *_stream = stream->real_stream;
@@ -40,7 +42,17 @@
 	if (stream->closed)
 		return;
 
-	_stream->cork(_stream);
+	_stream->cork(_stream, TRUE);
+}
+
+void o_stream_uncork(struct ostream *stream)
+{
+	struct _ostream *_stream = stream->real_stream;
+
+	if (stream->closed)
+		return;
+
+	_stream->cork(_stream, FALSE);
 }
 
 int o_stream_flush(struct ostream *stream)
@@ -53,11 +65,11 @@
 	return _stream->flush(_stream);
 }
 
-int o_stream_have_space(struct ostream *stream, size_t size)
+size_t o_stream_get_buffer_used_size(struct ostream *stream)
 {
 	struct _ostream *_stream = stream->real_stream;
 
-	return _stream->have_space(_stream, size);
+	return _stream->get_used_size(_stream);
 }
 
 int o_stream_seek(struct ostream *stream, uoff_t offset)
@@ -70,20 +82,28 @@
 	return _stream->seek(_stream, offset);
 }
 
-ssize_t o_stream_send(struct ostream *stream, const void *data, size_t size)
+int o_stream_send(struct ostream *stream, const void *data, size_t size)
+{
+	struct const_iovec iov;
+
+	iov.iov_base = data;
+	iov.iov_len = size;
+
+	return o_stream_sendv(stream, &iov, 1);
+}
+
+ssize_t o_stream_sendv(struct ostream *stream, const struct const_iovec *iov,
+		       size_t iov_count)
 {
 	struct _ostream *_stream = stream->real_stream;
 
 	if (stream->closed)
 		return -1;
 
-	if (size == 0)
-		return 0;
-
-	return _stream->send(_stream, data, size);
+	return _stream->sendv(_stream, iov, iov_count);
 }
 
-ssize_t o_stream_send_str(struct ostream *stream, const char *str)
+int o_stream_send_str(struct ostream *stream, const char *str)
 {
 	return o_stream_send(stream, str, strlen(str));
 }
--- a/src/lib/ostream.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/ostream.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,6 +1,8 @@
 #ifndef __OSTREAM_H
 #define __OSTREAM_H
 
+#include "ioloop.h"
+
 struct ostream {
 	uoff_t offset;
 
@@ -24,30 +26,29 @@
 /* Mark the stream closed. Nothing will be sent after this call. */
 void o_stream_close(struct ostream *stream);
 
+/* Set IO_WRITE callback. Default will just try to flush the output. */
+void o_stream_set_flush_callback(struct ostream *stream,
+				 io_callback_t *callback, void *context);
 /* Change the maximum size for stream's output buffer to grow. */
 void o_stream_set_max_buffer_size(struct ostream *stream, size_t max_size);
-/* Stream is made to be flushed out whenever it gets full (assumes max_size
-   is already set), ie. writes will never be partial. Also makes any blocking
-   writes to fail after specified timeout, calling timeout_cb if it's
-   set. This call changes non-blocking state of file descriptor. */
-void o_stream_set_blocking(struct ostream *stream, int timeout_msecs,
-			   void (*timeout_cb)(void *), void *context);
 
 /* Delays sending as far as possible, writing only full buffers. Also sets
-   TCP_CORK on if supported. o_stream_flush() removes the cork. */
+   TCP_CORK on if supported. */
 void o_stream_cork(struct ostream *stream);
+void o_stream_uncork(struct ostream *stream);
 /* Flush the output stream, blocks until everything is sent.
    Returns 1 if ok, -1 if error. */
 int o_stream_flush(struct ostream *stream);
-/* Returns 1 if specified amount of data currently fits into stream's output
-   buffer, 0 if not. */
-int o_stream_have_space(struct ostream *stream, size_t size);
+/* Returns number of bytes currently in buffer. */
+size_t o_stream_get_buffer_used_size(struct ostream *stream);
 
 /* Seek to specified position from beginning of file. This works only for
    files. Returns 1 if successful, -1 if error. */
 int o_stream_seek(struct ostream *stream, uoff_t offset);
-/* Returns number of bytes sent or buffered, or -1 if disconnected */
+/* Returns number of bytes sent, -1 = error */
 ssize_t o_stream_send(struct ostream *stream, const void *data, size_t size);
+ssize_t o_stream_sendv(struct ostream *stream, const struct const_iovec *iov,
+		       size_t iov_count);
 ssize_t o_stream_send_str(struct ostream *stream, const char *str);
 /* Send data from input stream. Returns number of bytes sent, or -1 if error.
    Note that this function may block if either instream or outstream is
--- a/src/lib/strfuncs.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/strfuncs.c	Sun Aug 15 06:40:30 2004 +0300
@@ -478,6 +478,13 @@
 	return strcasecmp(key, *member);
 }
 
+int strcasecmp_p(const void *p1, const void *p2)
+{
+	const char *const *s1 = p1, *const *s2 = p2;
+
+	return strcasecmp(*s1, *s2);
+}
+
 static const char **_strsplit(const char *data, const char *separators,
 			      int spaces)
 {
--- a/src/lib/strfuncs.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/lib/strfuncs.h	Sun Aug 15 06:40:30 2004 +0300
@@ -56,6 +56,7 @@
 int null_strcmp(const char *s1, const char *s2);
 int memcasecmp(const void *p1, const void *p2, size_t size);
 int bsearch_strcasecmp(const void *p1, const void *p2);
+int strcasecmp_p(const void *p1, const void *p2);
 
 /* seprators is an array of separator characters, not a separator string. */
 const char **t_strsplit(const char *data, const char *separators);
--- a/src/pop3-login/client-authenticate.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3-login/client-authenticate.c	Sun Aug 15 06:40:30 2004 +0300
@@ -58,7 +58,6 @@
 
 	client_send_line(client, msg != NULL ? t_strconcat("-ERR ", msg, NULL) :
 			 "-ERR Authentication failed.");
-	o_stream_flush(client->output);
 
 	/* get back to normal client input */
 	if (client->common.io != NULL)
@@ -91,6 +90,9 @@
 				  const unsigned char *data, size_t size)
 {
 	buffer_t *buf;
+	const void *buf_data;
+	size_t buf_size;
+	ssize_t ret;
 
 	t_push();
 
@@ -100,9 +102,11 @@
 	base64_encode(data, size, buf);
 	buffer_append(buf, "\r\n", 2);
 
-	o_stream_send(client->output, buffer_get_data(buf, NULL),
-		      buffer_get_used_size(buf));
-	o_stream_flush(client->output);
+	buf_data = buffer_get_data(buf, &buf_size);
+	if ((ret = o_stream_send(client->output, buf_data, buf_size) < 0))
+		client_destroy(client, "Disconnected");
+	else if ((size_t)ret != buf_size)
+		client_destroy(client, "Transmit buffer full");
 
 	t_pop();
 }
@@ -393,6 +397,8 @@
 		buffer_get_data(apop_data, &info.initial_resp_size);
 
 	client_ref(client);
+	o_stream_uncork(client->output);
+
 	client->common.auth_request =
 		auth_client_request_new(auth_client, &client->auth_id, &info,
 					login_callback, client, &error);
--- a/src/pop3-login/client.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3-login/client.c	Sun Aug 15 06:40:30 2004 +0300
@@ -16,8 +16,13 @@
 #include "hostpid.h"
 #include "imem.h"
 
-/* max. length of input command line (spec says 512) */
-#define MAX_INBUF_SIZE 2048
+/* max. length of input command line (spec says 512), or max reply length in
+   SASL authentication */
+#define MAX_INBUF_SIZE 4096
+
+/* max. size of output buffer. if it gets full, the client is disconnected.
+   SASL authentication gives the largest output. */
+#define MAX_OUTBUF_SIZE 4096
 
 /* Disconnect client after idling this many seconds */
 #define CLIENT_LOGIN_IDLE_TIMEOUT 60
@@ -54,14 +59,54 @@
 
 static void client_open_streams(struct pop3_client *client, int fd)
 {
-	client->input = i_stream_create_file(fd, default_pool, 8192, FALSE);
-	client->output = o_stream_create_file(fd, default_pool, 1024, FALSE);
+	client->input = i_stream_create_file(fd, default_pool,
+					     MAX_INBUF_SIZE, FALSE);
+	client->output = o_stream_create_file(fd, default_pool,
+					      MAX_OUTBUF_SIZE, FALSE);
+}
+
+static void client_start_tls(struct pop3_client *client)
+{
+	int fd_ssl;
+
+	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
+			       &client->common.proxy);
+	if (fd_ssl == -1) {
+		client_send_line(client, "-ERR TLS initialization failed.");
+		client_destroy(client, "TLS initialization failed.");
+		return;
+	}
+
+	client->tls = TRUE;
+	client->secured = TRUE;
+	client_set_title(client);
+
+	client->common.fd = fd_ssl;
+
+	i_stream_unref(client->input);
+	o_stream_unref(client->output);
+
+	client_open_streams(client, fd_ssl);
+	client->common.io = io_add(client->common.fd, IO_READ,
+				   client_input, client);
+}
+
+static void client_output_starttls(void *context)
+{
+	struct pop3_client *client = context;
+	int ret;
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client, "Disconnected");
+		return;
+	}
+
+	if (ret > 0)
+		client_start_tls(client);
 }
 
 static int cmd_stls(struct pop3_client *client)
 {
-	int fd_ssl;
-
 	if (client->tls) {
 		client_send_line(client, "-ERR TLS is already active.");
 		return TRUE;
@@ -72,36 +117,21 @@
 		return TRUE;
 	}
 
-	client_send_line(client, "+OK Begin TLS negotiation now.");
-	o_stream_flush(client->output);
-
-	/* must be removed before ssl_proxy_new(), since it may
-	   io_add() the same fd. */
+	/* remove input handler, SSL proxy gives us a new fd. we also have to
+	   remove it in case we have to wait for buffer to be flushed */
 	if (client->common.io != NULL) {
 		io_remove(client->common.io);
 		client->common.io = NULL;
 	}
 
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
-			       &client->common.proxy);
-	if (fd_ssl != -1) {
-		client->tls = TRUE;
-		client->secured = TRUE;
-                client_set_title(client);
-
-		client->common.fd = fd_ssl;
-
-		i_stream_unref(client->input);
-		o_stream_unref(client->output);
-
-		client_open_streams(client, fd_ssl);
-		client->common.io = io_add(client->common.fd, IO_READ,
-					   client_input, client);
+	client_send_line(client, "+OK Begin TLS negotiation now.");
+	if (o_stream_get_buffer_used_size(client->output) != 0) {
+		/* the buffer has to be flushed */
+		o_stream_set_flush_callback(client->output,
+					    client_output_starttls, client);
 	} else {
-		client_send_line(client, "-ERR TLS initialization failed.");
-		client_destroy(client, "TLS initialization failed.");
+		client_start_tls(client);
 	}
-
 	return TRUE;
 }
 
@@ -184,7 +214,7 @@
 	}
 
 	if (client_unref(client))
-		o_stream_flush(client->output);
+		o_stream_uncork(client->output);
 }
 
 static void client_destroy_oldest(void)
@@ -347,8 +377,18 @@
 
 void client_send_line(struct pop3_client *client, const char *line)
 {
-	o_stream_send_str(client->output, line);
-	o_stream_send(client->output, "\r\n", 2);
+	struct const_iovec iov[2];
+	ssize_t ret;
+
+	iov[0].iov_base = line;
+	iov[0].iov_len = strlen(line);
+	iov[1].iov_base = "\r\n";
+	iov[1].iov_len = 2;
+
+	if ((ret = o_stream_sendv(client->output, iov, 2)) < 0)
+		client_destroy(client, "Disconnected");
+	else if ((size_t)ret != iov[0].iov_len + iov[1].iov_len)
+		client_destroy(client, "Transmit buffer full");
 }
 
 void client_syslog(struct pop3_client *client, const char *text)
--- a/src/pop3/client.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3/client.c	Sun Aug 15 06:40:30 2004 +0300
@@ -5,6 +5,7 @@
 #include "network.h"
 #include "istream.h"
 #include "ostream.h"
+#include "str.h"
 #include "mail-storage.h"
 #include "commands.h"
 #include "mail-search.h"
@@ -14,7 +15,11 @@
 /* max. length of input command line (spec says 512) */
 #define MAX_INBUF_SIZE 2048
 
-/* If we can't send a buffer in a minute, disconnect the client */
+/* Stop reading input when output buffer has this many bytes. Once the buffer
+   size has dropped to half of it, start reading input again. */
+#define OUTBUF_THROTTLE_SIZE 4096
+
+/* If we can't send anything for a minute, disconnect the client */
 #define CLIENT_OUTPUT_TIMEOUT (60*1000)
 
 /* Disconnect client when it sends too many bad commands in a row */
@@ -29,14 +34,7 @@
 static struct timeout *to_idle;
 
 static void client_input(void *context);
-
-static void client_output_timeout(void *context)
-{
-	struct client *client = context;
-
-	i_stream_close(client->input);
-	o_stream_close(client->output);
-}
+static void client_output(void *context);
 
 static int sync_mailbox(struct mailbox *box)
 {
@@ -131,14 +129,16 @@
 	struct client *client;
         enum mailbox_open_flags flags;
 
+	/* always use nonblocking I/O */
+	net_set_nonblock(hin, TRUE);
+	net_set_nonblock(hout, TRUE);
+
 	client = i_new(struct client, 1);
 	client->input = i_stream_create_file(hin, default_pool,
 					     MAX_INBUF_SIZE, FALSE);
-	client->output = o_stream_create_file(hout, default_pool, 4096, FALSE);
-
-	/* set timeout for sending data */
-	o_stream_set_blocking(client->output, CLIENT_OUTPUT_TIMEOUT,
-			      client_output_timeout, client);
+	client->output = o_stream_create_file(hout, default_pool,
+					      (size_t)-1, FALSE);
+	o_stream_set_flush_callback(client->output, client_output, client);
 
 	client->io = io_add(hin, IO_READ, client_input, client);
         client->last_input = ioloop_time;
@@ -171,8 +171,6 @@
 
 void client_destroy(struct client *client)
 {
-	o_stream_flush(client->output);
-
 	if (client->mailbox != NULL)
 		mailbox_close(client->mailbox);
 	mail_storage_destroy(client->storage);
@@ -180,7 +178,8 @@
 	i_free(client->message_sizes);
 	i_free(client->deleted_bitmask);
 
-	io_remove(client->io);
+	if (client->io != NULL)
+		io_remove(client->io);
 
 	i_stream_unref(client->input);
 	o_stream_unref(client->output);
@@ -194,25 +193,52 @@
 
 void client_disconnect(struct client *client)
 {
-	o_stream_flush(client->output);
+	(void)o_stream_flush(client->output);
 
 	i_stream_close(client->input);
 	o_stream_close(client->output);
 }
 
-void client_send_line(struct client *client, const char *fmt, ...)
+int client_send_line(struct client *client, const char *fmt, ...)
 {
 	va_list va;
+	string_t *str;
+	ssize_t ret;
 
 	if (client->output->closed)
-		return;
+		return -1;
 
 	t_push();
 	va_start(va, fmt);
-	(void)o_stream_send_str(client->output, t_strdup_vprintf(fmt, va));
-	(void)o_stream_send(client->output, "\r\n", 2);
+
+	str = t_str_new(256);
+	str_vprintfa(str, fmt, va);
+	str_append(str, "\r\n");
+
+	ret = o_stream_send(client->output, str_data(str), str_len(str));
+	if (ret < 0)
+		client_destroy(client);
+	else {
+		i_assert((size_t)ret == str_len(str));
+
+		if (o_stream_get_buffer_used_size(client->output) <
+		    OUTBUF_THROTTLE_SIZE) {
+			ret = 1;
+			client->last_output = ioloop_time;
+		} else {
+			ret = 0;
+			if (client->io != NULL) {
+				/* no more input until client has read
+				   our output */
+				io_remove(client->io);
+				client->io = NULL;
+			}
+		}
+	}
+
 	va_end(va);
 	t_pop();
+	return (int)ret;
 }
 
 void client_send_storage_error(struct client *client)
@@ -237,6 +263,16 @@
 	struct client *client = context;
 	char *line, *args;
 
+	if (client->cmd != NULL) {
+		/* we're still processing a command. wait until it's
+		   finished. */
+		io_remove(client->io);
+		client->io = NULL;
+		client->waiting_input = TRUE;
+		return;
+	}
+
+	client->waiting_input = FALSE;
 	client->last_input = ioloop_time;
 
 	switch (i_stream_read(client->input)) {
@@ -260,28 +296,63 @@
 		else
 			*args++ = '\0';
 
-		if (client_command_execute(client, line, args))
+		if (client_command_execute(client, line, args)) {
 			client->bad_counter = 0;
-		else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
+			if (client->cmd != NULL) {
+				client->waiting_input = TRUE;
+				break;
+			}
+		} else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
 			client_send_line(client, "-ERR Too many bad commands.");
 			client_disconnect(client);
 		}
 	}
-	o_stream_flush(client->output);
+	o_stream_uncork(client->output);
 
 	if (client->output->closed)
 		client_destroy(client);
 }
 
+static void client_output(void *context)
+{
+	struct client *client = context;
+	int ret;
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client);
+		return;
+	}
+
+	client->last_output = ioloop_time;
+
+	if (o_stream_get_buffer_used_size(client->output) <
+	    OUTBUF_THROTTLE_SIZE/2 && client->io == NULL &&
+	    client->cmd == NULL) {
+		/* enable input again */
+		client->io = io_add(i_stream_get_fd(client->input), IO_READ,
+				    client_input, client);
+		if (client->waiting_input)
+			client_input(client);
+	}
+}
+
 static void idle_timeout(void *context __attr_unused__)
 {
 	if (my_client == NULL)
 		return;
 
-	if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) {
-		client_send_line(my_client,
-				 "-ERR Disconnected for inactivity.");
-		client_destroy(my_client);
+	if (my_client->cmd != NULL) {
+		if (ioloop_time - my_client->last_output >=
+		    CLIENT_OUTPUT_TIMEOUT &&
+		    my_client->last_input < my_client->last_output)
+			client_destroy(my_client);
+	} else {
+		if (ioloop_time - my_client->last_input >=
+		    CLIENT_IDLE_TIMEOUT) {
+			client_send_line(my_client,
+					 "-ERR Disconnected for inactivity.");
+			client_destroy(my_client);
+		}
 	}
 }
 
--- a/src/pop3/client.h	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3/client.h	Sun Aug 15 06:40:30 2004 +0300
@@ -1,18 +1,24 @@
 #ifndef __CLIENT_H
 #define __CLIENT_H
 
+struct client;
 struct mail_storage;
 
+typedef void command_func_t(struct client *client);
+
 struct client {
 	int socket;
 	struct io *io;
 	struct istream *input;
 	struct ostream *output;
 
+	command_func_t *cmd;
+	void *cmd_context;
+
 	struct mail_storage *storage;
 	struct mailbox *mailbox;
 
-	time_t last_input;
+	time_t last_input, last_output;
 	unsigned int bad_counter;
 
 	unsigned int messages_count;
@@ -24,6 +30,7 @@
 	unsigned char *deleted_bitmask;
 
 	unsigned int deleted:1;
+	unsigned int waiting_input:1;
 };
 
 /* Create new client with specified input/output handles. socket specifies
@@ -35,7 +42,7 @@
 void client_disconnect(struct client *client);
 
 /* Send a line of data to client */
-void client_send_line(struct client *client, const char *fmt, ...)
+int client_send_line(struct client *client, const char *fmt, ...)
 	__attr_format__(2, 3);
 void client_send_storage_error(struct client *client);
 
--- a/src/pop3/commands.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3/commands.c	Sun Aug 15 06:40:30 2004 +0300
@@ -111,23 +111,48 @@
 	return TRUE;
 }
 
+struct cmd_list_context {
+	unsigned int msgnum;
+};
+
+static void cmd_list_callback(struct client *client)
+{
+	struct cmd_list_context *ctx = client->cmd_context;
+	int ret;
+
+	for (; ctx->msgnum != client->messages_count; ctx->msgnum++) {
+		if (client->deleted) {
+			if (client->deleted_bitmask[ctx->msgnum / CHAR_BIT] &
+			    (1 << (ctx->msgnum % CHAR_BIT)))
+				continue;
+		}
+		ret = client_send_line(client, "%u %"PRIuUOFF_T,
+				       ctx->msgnum+1,
+				       client->message_sizes[ctx->msgnum]);
+		if (ret < 0)
+			break;
+		if (ret == 0)
+			return;
+	}
+
+	client_send_line(client, ".");
+
+	i_free(ctx);
+	client->cmd = NULL;
+}
+
 static int cmd_list(struct client *client, const char *args)
 {
-	unsigned int i;
+        struct cmd_list_context *ctx;
 
 	if (*args == '\0') {
+		ctx = i_new(struct cmd_list_context, 1);
 		client_send_line(client, "+OK %u messages:",
 				 client->messages_count - client->deleted_count);
-		for (i = 0; i < client->messages_count; i++) {
-			if (client->deleted) {
-				if (client->deleted_bitmask[i / CHAR_BIT] &
-				    (1 << (i % CHAR_BIT)))
-					continue;
-			}
-			client_send_line(client, "%u %"PRIuUOFF_T,
-					 i+1, client->message_sizes[i]);
-		}
-		client_send_line(client, ".");
+
+		client->cmd = cmd_list_callback;
+		client->cmd_context = ctx;
+		cmd_list_callback(client);
 	} else {
 		unsigned int msgnum;
 
@@ -198,127 +223,149 @@
 	return TRUE;
 }
 
-static void stream_send_escaped(struct ostream *output, struct istream *input,
-				uoff_t body_lines)
+struct fetch_context {
+        struct mailbox_transaction_context *t;
+	struct mail_search_context *search_ctx;
+	struct istream *stream;
+	uoff_t body_lines;
+
+	struct mail_search_arg search_arg;
+        struct mail_search_seqset seqset;
+
+	unsigned char last;
+	int cr_skipped, in_body;
+};
+
+static void fetch_deinit(struct fetch_context *ctx)
 {
+	(void)mailbox_search_deinit(ctx->search_ctx);
+	(void)mailbox_transaction_commit(ctx->t);
+	i_free(ctx);
+}
+
+static void fetch_callback(struct client *client)
+{
+	struct fetch_context *ctx = client->cmd_context;
 	const unsigned char *data;
-	unsigned char last, add;
+	unsigned char add;
 	size_t i, size;
-	int cr_skipped, in_header;
+	ssize_t ret;
+
+	o_stream_set_max_buffer_size(client->output, 0);
 
-	if (body_lines != (uoff_t)-1)
-		body_lines++; /* internally we count the empty line too */
+	while ((ctx->body_lines > 0 || !ctx->in_body) &&
+	       i_stream_read_data(ctx->stream, &data, &size, 0) > 0) {
+		if (size > 4096)
+			size = 4096;
 
-	cr_skipped = FALSE; in_header = TRUE; last = '\0';
-	while ((body_lines > 0 || in_header) &&
-	       i_stream_read_data(input, &data, &size, 0) > 0) {
 		add = '\0';
 		for (i = 0; i < size; i++) {
-			if (in_header && (data[i] == '\r' || data[i] == '\n')) {
-				if (i == 0 && (last == '\0' || last == '\n'))
-					in_header = FALSE;
+			if ((data[i] == '\r' || data[i] == '\n') &&
+			    !ctx->in_body) {
+				if (i == 0 && (ctx->last == '\0' ||
+					       ctx->last == '\n'))
+					ctx->in_body = TRUE;
 				else if (i > 0 && data[i-1] == '\n')
-					in_header = FALSE;
+					ctx->in_body = TRUE;
 			}
 
 			if (data[i] == '\n') {
-				if ((i == 0 && last != '\r') ||
+				if ((i == 0 && ctx->last != '\r') ||
 				    (i > 0 && data[i-1] != '\r')) {
 					/* missing CR */
 					add = '\r';
 					break;
 				}
 
-				if (!in_header) {
-					if (--body_lines == 0) {
+				if (ctx->in_body) {
+					if (--ctx->body_lines == 0) {
 						i++;
 						break;
 					}
 				}
 			} else if (data[i] == '.' &&
-				   ((i == 0 && last == '\n') ||
+				   ((i == 0 && ctx->last == '\n') ||
 				    (i > 0 && data[i-1] == '\n'))) {
 				/* escape the dot */
 				add = '.';
-				i++;
 				break;
 			} else if (data[i] == '\0' &&
 				   (client_workarounds &
 				    WORKAROUND_OUTLOOK_NO_NULS) != 0) {
-				add = '\x80';
+				add = 0x80;
 				break;
 			}
 		}
 
-		if (o_stream_send(output, data, i) < 0)
+		if ((ret = o_stream_send(client->output, data, i)) < 0)
+			break;
+		if (ret > 0)
+			ctx->last = data[ret-1];
+		i_stream_skip(ctx->stream, ret);
+
+		if ((size_t)ret != i) {
+			/* continue later */
 			return;
+		}
 
 		if (add != '\0') {
-			if (o_stream_send(output, &add, 1) < 0)
+			if ((ret = o_stream_send(client->output, &add, 1)) < 0)
+				break;
+			if (ret == 0)
 				return;
-			last = add;
-			if (client_workarounds & WORKAROUND_OUTLOOK_NO_NULS) {
-				if (i < size && data[i] == '\0')
-					i++;
-			}
-		} else {
-			last = data[i-1];
+
+			ctx->last = add;
+			if (add == 0x80)
+				i_stream_skip(ctx->stream, 1);
 		}
+	}
+	o_stream_set_max_buffer_size(client->output, (size_t)-1);
 
-		i_stream_skip(input, i);
+	if (ctx->last != '\n') {
+		/* didn't end with CRLF */
+		(void)o_stream_send(client->output, "\r\n", 2);
 	}
 
-	if (last != '\n') {
-		/* didn't end with CRLF */
-		(void)o_stream_send(output, "\r\n", 2);
-	}
+	client_send_line(client, ".");
+	fetch_deinit(ctx);
+	client->cmd = NULL;
 }
 
-static void fetch(struct client *client, unsigned int msgnum,
-		  uoff_t body_lines)
+static void fetch(struct client *client, unsigned int msgnum, uoff_t body_lines)
 {
-	struct mail_search_arg search_arg;
-        struct mail_search_seqset seqset;
-        struct mailbox_transaction_context *t;
-	struct mail_search_context *ctx;
+        struct fetch_context *ctx;
 	struct mail *mail;
-	struct istream *stream;
+
+	ctx = i_new(struct fetch_context, 1);
 
-	memset(&seqset, 0, sizeof(seqset));
-	seqset.seq1 = seqset.seq2 = msgnum+1;
-
-	memset(&search_arg, 0, sizeof(search_arg));
-	search_arg.type = SEARCH_SEQSET;
-	search_arg.value.seqset = &seqset;
+	ctx->seqset.seq1 = ctx->seqset.seq2 = msgnum+1;
+	ctx->search_arg.type = SEARCH_SEQSET;
+	ctx->search_arg.value.seqset = &ctx->seqset;
 
-	t = mailbox_transaction_begin(client->mailbox, FALSE);
-	ctx = mailbox_search_init(t, NULL, &search_arg, NULL,
-				  MAIL_FETCH_STREAM_HEADER |
-				  MAIL_FETCH_STREAM_BODY, NULL);
-	if (ctx == NULL) {
-		mailbox_transaction_rollback(t);
-		client_send_storage_error(client);
+	ctx->t = mailbox_transaction_begin(client->mailbox, FALSE);
+	ctx->search_ctx = mailbox_search_init(ctx->t, NULL, &ctx->search_arg,
+					      NULL, MAIL_FETCH_STREAM_HEADER |
+					      MAIL_FETCH_STREAM_BODY, NULL);
+	mail = mailbox_search_next(ctx->search_ctx);
+	ctx->stream = mail == NULL ? NULL : mail->get_stream(mail, NULL, NULL);
+	if (ctx->stream == NULL) {
+		client_send_line(client, "-ERR Message not found.");
+		fetch_deinit(ctx);
 		return;
 	}
 
-	mail = mailbox_search_next(ctx);
-	stream = mail == NULL ? NULL : mail->get_stream(mail, NULL, NULL);
-	if (stream == NULL)
-		client_send_line(client, "-ERR Message not found.");
-	else {
-		if (body_lines == (uoff_t)-1) {
-			client_send_line(client, "+OK %"PRIuUOFF_T" octets",
-					 client->message_sizes[msgnum]);
-		} else {
-			client_send_line(client, "+OK");
-		}
-
-		stream_send_escaped(client->output, stream, body_lines);
-		client_send_line(client, ".");
+	ctx->body_lines = body_lines;
+	if (body_lines == (uoff_t)-1) {
+		client_send_line(client, "+OK %"PRIuUOFF_T" octets",
+				 client->message_sizes[msgnum]);
+	} else {
+		client_send_line(client, "+OK");
+		ctx->body_lines++; /* internally we count the empty line too */
 	}
 
-	(void)mailbox_search_deinit(ctx);
-	(void)mailbox_transaction_commit(t);
+	client->cmd = fetch_callback;
+	client->cmd_context = ctx;
+	fetch_callback(client);
 }
 
 static int cmd_retr(struct client *client, const char *args)
@@ -368,37 +415,22 @@
 	return TRUE;
 }
 
-static void list_uids(struct client *client, unsigned int message)
-{
+struct cmd_uidl_context {
+        struct mailbox_transaction_context *t;
+	struct mail_search_context *search_ctx;
+	unsigned int message;
+
 	struct mail_search_arg search_arg;
 	struct mail_search_seqset seqset;
-        struct mailbox_transaction_context *t;
-	struct mail_search_context *ctx;
+};
+
+static int list_uids_iter(struct client *client, struct cmd_uidl_context *ctx)
+{
 	struct mail *mail;
 	const char *uid_str;
-	int found = FALSE;
-
-	if (client->messages_count == 0 && message == 0)
-		return;
+	int ret, found = FALSE;
 
-	memset(&search_arg, 0, sizeof(search_arg));
-	if (message == 0)
-		search_arg.type = SEARCH_ALL;
-	else {
-		seqset.seq1 = seqset.seq2 = message;
-		search_arg.type = SEARCH_SEQSET;
-		search_arg.value.seqset = &seqset;
-	}
-
-	t = mailbox_transaction_begin(client->mailbox, FALSE);
-	ctx = mailbox_search_init(t, NULL, &search_arg, NULL, 0, NULL);
-	if (ctx == NULL) {
-		mailbox_transaction_rollback(t);
-		client_send_storage_error(client);
-		return;
-	}
-
-	while ((mail = mailbox_search_next(ctx)) != NULL) {
+	while ((mail = mailbox_search_next(ctx->search_ctx)) != NULL) {
 		if (client->deleted) {
 			uint32_t idx = mail->seq - 1;
 			if (client->deleted_bitmask[idx / CHAR_BIT] &
@@ -407,31 +439,81 @@
 		}
 
 		uid_str = mail->get_special(mail, MAIL_FETCH_UID_STRING);
-		client_send_line(client, message == 0 ? "%u %s" : "+OK %u %s",
-				 mail->seq, uid_str);
 		found = TRUE;
+
+		ret = client_send_line(client, ctx->message == 0 ?
+				       "%u %s" : "+OK %u %s",
+				       mail->seq, uid_str);
+		if (ret < 0)
+			break;
+		if (ret == 0 && ctx->message == 0) {
+			/* output is being buffered, continue when there's
+			   more space */
+			return 0;
+		}
 	}
 
-	(void)mailbox_search_deinit(ctx);
-	(void)mailbox_transaction_commit(t);
+	/* finished */
+	(void)mailbox_search_deinit(ctx->search_ctx);
+	(void)mailbox_transaction_commit(ctx->t);
+
+	client->cmd = NULL;
+
+	if (ctx->message == 0)
+		client_send_line(client, ".");
+	i_free(ctx);
+	return found;
+}
+
+static void cmd_uidl_callback(struct client *client)
+{
+	struct cmd_uidl_context *ctx = client->cmd_context;
+
+        (void)list_uids_iter(client, ctx);
+}
 
-	if (!found && message != 0)
-		client_send_line(client, "-ERR Message not found.");
+static struct cmd_uidl_context *
+cmd_uidl_init(struct client *client, unsigned int message)
+{
+        struct cmd_uidl_context *ctx;
+
+	ctx = i_new(struct cmd_uidl_context, 1);
+
+	if (message == 0)
+		ctx->search_arg.type = SEARCH_ALL;
+	else {
+		ctx->seqset.seq1 = ctx->seqset.seq2 = message;
+		ctx->search_arg.type = SEARCH_SEQSET;
+		ctx->search_arg.value.seqset = &ctx->seqset;
+	}
+
+	ctx->t = mailbox_transaction_begin(client->mailbox, FALSE);
+	ctx->search_ctx = mailbox_search_init(ctx->t, NULL, &ctx->search_arg,
+					      NULL, 0, NULL);
+	if (message == 0) {
+		client->cmd = cmd_uidl_callback;
+		client->cmd_context = ctx;
+	}
+	return ctx;
 }
 
 static int cmd_uidl(struct client *client, const char *args)
 {
+        struct cmd_uidl_context *ctx;
+
 	if (*args == '\0') {
 		client_send_line(client, "+OK");
-		list_uids(client, 0);
-		client_send_line(client, ".");
+		ctx = cmd_uidl_init(client, 0);
+		list_uids_iter(client, ctx);
 	} else {
 		unsigned int msgnum;
 
 		if (get_msgnum(client, args, &msgnum) == NULL)
 			return FALSE;
 
-		list_uids(client, msgnum+1);
+		ctx = cmd_uidl_init(client, msgnum+1);
+		if (list_uids_iter(client, ctx))
+			client_send_line(client, "-ERR Message not found.");
 	}
 
 	return TRUE;