changeset 14623:df8ba29d9eb3

imap: Rewrote FETCH command to use imap-msgpart API.
author Timo Sirainen <tss@iki.fi>
date Thu, 21 Jun 2012 21:52:56 +0300
parents 6fb61872b30a
children 3a8ada7302a3
files src/imap/cmd-select.c src/imap/imap-client.h src/imap/imap-fetch-body.c src/imap/imap-fetch.c src/imap/imap-fetch.h
diffstat 5 files changed, 156 insertions(+), 678 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/cmd-select.c	Thu Jun 21 21:50:35 2012 +0300
+++ b/src/imap/cmd-select.c	Thu Jun 21 21:52:56 2012 +0300
@@ -301,7 +301,6 @@
 				STATUS_HIGHESTMODSEQ, &status);
 
 	client->mailbox = ctx->box;
-	client->select_counter++;
 	client->mailbox_examined = readonly;
 	client->messages_count = status.messages;
 	client->recent_count = status.recent;
--- a/src/imap/imap-client.h	Thu Jun 21 21:50:35 2012 +0300
+++ b/src/imap/imap-client.h	Thu Jun 21 21:52:56 2012 +0300
@@ -78,15 +78,6 @@
 	unsigned int temp_executed:1; /* temporary execution state tracking */
 };
 
-struct partial_fetch_cache {
-	unsigned int select_counter;
-	unsigned int uid;
-
-	uoff_t physical_start;
-	bool cr_skipped;
-	struct message_size pos;
-};
-
 struct imap_client_vfuncs {
 	void (*destroy)(struct client *client, const char *reason);
 };
@@ -111,7 +102,6 @@
         struct mail_user *user;
 	struct mailbox *mailbox;
         struct mailbox_keywords keywords;
-	unsigned int select_counter; /* increased when mailbox is changed */
 	unsigned int sync_counter;
 	uint32_t messages_count, recent_count, uidvalidity;
 	enum mailbox_feature enabled_features;
@@ -130,8 +120,6 @@
 	uint64_t sync_last_full_modseq;
 	uint64_t highest_fetch_modseq;
 
-	struct partial_fetch_cache last_partial;
-
 	/* SEARCHRES extension: Last saved SEARCH result */
 	ARRAY_TYPE(seq_range) search_saved_uidset;
 	/* SEARCH=CONTEXT extension: Searches that get updated */
--- a/src/imap/imap-fetch-body.c	Thu Jun 21 21:50:35 2012 +0300
+++ b/src/imap/imap-fetch-body.c	Thu Jun 21 21:52:56 2012 +0300
@@ -10,6 +10,7 @@
 #include "message-parser.h"
 #include "message-send.h"
 #include "mail-storage-private.h"
+#include "imap-quote.h"
 #include "imap-parser.h"
 #include "imap-msgpart.h"
 #include "imap-fetch.h"
@@ -19,17 +20,10 @@
 #include <unistd.h>
 
 struct imap_fetch_body_data {
-	struct imap_fetch_body_data *next;
+	const char *section; /* NOTE: always uppercased */
+	struct imap_msgpart *msgpart;
 
-        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 */
-
-	const char *const *fields;
-	size_t fields_count;
-
-	unsigned int skip_set:1;
+	unsigned int partial:1;
 	unsigned int peek:1;
 };
 
@@ -42,54 +36,16 @@
 		mailbox_get_vname(ctx->cur_mail->box), ctx->cur_mail->uid);
 }
 
-static int seek_partial(unsigned int select_counter, unsigned int uid,
-			struct partial_fetch_cache *partial,
-			struct istream *stream,
-			uoff_t virtual_skip, bool *cr_skipped_r)
-{
-	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);
-	if (message_skip_virtual(stream, virtual_skip, &partial->pos,
-				 partial->cr_skipped, cr_skipped_r) < 0)
-		return -1;
-
-	partial->cr_skipped = FALSE;
-	return 0;
-}
-
-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 const char *get_body_name(const struct imap_fetch_body_data *body)
 {
 	string_t *str;
 
 	str = t_str_new(128);
 	str_printfa(str, "BODY[%s]", body->section);
-	if (body->skip_set)
-		str_printfa(str, "<%"PRIuUOFF_T">", body->skip);
+	if (body->partial) {
+		str_printfa(str, "<%"PRIuUOFF_T">",
+			    imap_msgpart_get_partial_offset(body->msgpart));
+	}
 	return str_c(str);
 }
 
@@ -114,131 +70,7 @@
 	return str;
 }
 
-static off_t imap_fetch_send(struct imap_fetch_context *ctx,
-			     struct ostream *output, struct istream *input,
-			     bool cr_skipped, uoff_t virtual_size,
-			     bool add_missing_eoh, bool *last_cr)
-{
-	const unsigned char *msg;
-	size_t i, size;
-	uoff_t vsize_left, sent;
-	off_t ret;
-	unsigned char add;
-	bool 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 ((uoff_t)ret < i) {
-			add = '\0';
-			blocks = TRUE;
-		}
-
-		if (ret > 0)
-			cr_skipped = msg[ret-1] == '\r';
-
-		i_stream_skip(input, ret);
-		sent += ret;
-
-		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 (input->stream_errno != 0) {
-		fetch_read_error(ctx);
-		return -1;
-	}
-
-	if (add_missing_eoh && sent + 2 == virtual_size) {
-		/* Netscape missing EOH workaround. */
-		o_stream_set_max_buffer_size(output, (size_t)-1);
-		if (o_stream_send(output, "\r\n", 2) < 0)
-			return -1;
-		sent += 2;
-	}
-
-	if ((uoff_t)sent != virtual_size && !blocks) {
-		/* Input stream gave less data than 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. */
-		i_error("FETCH %s for mailbox %s UID %u got too little data: "
-			"%"PRIuUOFF_T" vs %"PRIuUOFF_T, ctx->cur_name,
-			mailbox_get_vname(ctx->cur_mail->box),
-			ctx->cur_mail->uid, (uoff_t)sent, virtual_size);
-		mail_set_cache_corrupted(ctx->cur_mail, ctx->cur_size_field);
-		client_disconnect(ctx->client, "FETCH failed");
-		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, 4096);
-	ret = imap_fetch_send(ctx, ctx->client->output, ctx->cur_input,
-			      ctx->skip_cr, ctx->cur_size - ctx->cur_offset,
-			      ctx->cur_append_eoh, &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) {
-		struct partial_fetch_cache *p = &ctx->client->last_partial;
-
-		p->cr_skipped = ctx->skip_cr != 0;
-		p->pos.physical_size =
-			ctx->cur_input->v_offset - p->physical_start;
-		p->pos.virtual_size += ret;
-	}
-
-	return ctx->cur_offset == ctx->cur_size;
-}
-
-static int fetch_stream_send_direct(struct imap_fetch_context *ctx)
+static int fetch_stream_continue(struct imap_fetch_context *ctx)
 {
 	off_t ret;
 
@@ -246,25 +78,20 @@
 	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;
-
-	if (ctx->cur_append_eoh && ctx->cur_offset + 2 == ctx->cur_size) {
-		/* Netscape missing EOH workaround. */
-		if (o_stream_send(ctx->client->output, "\r\n", 2) < 0)
-			return -1;
-		ctx->cur_offset += 2;
-		ctx->cur_append_eoh = FALSE;
-	}
+	if (ret > 0)
+		ctx->cur_offset += ret;
 
 	if (ctx->cur_offset != ctx->cur_size) {
 		/* unfinished */
+		if (ctx->cur_input->stream_errno != 0) {
+			fetch_read_error(ctx);
+			client_disconnect(ctx->client, "FETCH failed");
+			return -1;
+		}
 		if (!i_stream_have_bytes_left(ctx->cur_input)) {
 			/* Input stream gave less data than expected */
 			i_error("FETCH %s for mailbox %s UID %u "
-				"got too little data (copying): "
+				"got too little data: "
 				"%"PRIuUOFF_T" vs %"PRIuUOFF_T,
 				ctx->cur_name, mailbox_get_vname(ctx->cur_mail->box),
 				ctx->cur_mail->uid, ctx->cur_offset, ctx->cur_size);
@@ -273,6 +100,10 @@
 			client_disconnect(ctx->client, "FETCH failed");
 			return -1;
 		}
+		if (ret < 0) {
+			/* client probably disconnected */
+			return -1;
+		}
 
 		o_stream_set_flush_pending(ctx->client->output, TRUE);
 		return 0;
@@ -280,379 +111,30 @@
 	return 1;
 }
 
-static int fetch_stream(struct imap_fetch_context *ctx,
-			const struct message_size *size)
+static int fetch_body_msgpart(struct imap_fetch_context *ctx, struct mail *mail,
+			      const struct imap_fetch_body_data *body)
 {
-	struct istream *input;
+	struct imap_msgpart_open_result result;
+	string_t *str;
 
-	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(ctx->cur_input, ctx->cur_size);
-		i_stream_unref(&ctx->cur_input);
-		ctx->cur_input = input;
+	if (imap_msgpart_open(mail, body->msgpart, &result) < 0)
+		return -1;
+	ctx->cur_input = result.input;
+	ctx->cur_size = result.size;
+	ctx->cur_size_field = result.size_field;
+	ctx->cur_name = p_strconcat(ctx->pool, "[", body->section, "]", NULL);
 
-		ctx->cont_handler = fetch_stream_send_direct;
-	} else {
-                ctx->cont_handler = fetch_stream_send;
-	}
+	str = get_prefix(ctx, body, ctx->cur_size);
+	if (o_stream_send(ctx->client->output, str_data(str), str_len(str)) < 0)
+		return -1;
 
+	ctx->cont_handler = fetch_stream_continue;
 	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_name = p_strconcat(ctx->pool,
-				    "[", body->section, "]", NULL);
-	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) {
-		if (message_skip_virtual(ctx->cur_input, body->skip, NULL,
-					 FALSE, &ctx->skip_cr) < 0) {
-			fetch_read_error(ctx);
-			return -1;
-		}
-	} else {
-		if (seek_partial(ctx->select_counter, ctx->cur_mail->uid,
-				 &ctx->client->last_partial, ctx->cur_input,
-				 body->skip, &ctx->skip_cr) < 0) {
-			fetch_read_error(ctx);
-			return -1;
-		}
-	}
-
-	return fetch_stream(ctx, size);
-}
-
-static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
-		      const struct imap_fetch_body_data *body)
-{
-	const struct message_size *fetch_size;
-	struct istream *input;
-	struct message_size hdr_size, body_size;
-
-	switch (body->section[0]) {
-	case '\0':
-		/* BODY[] - fetch everything */
-		if (mail_get_stream(mail, NULL, NULL, &input) < 0 ||
-		    mail_get_virtual_size(mail, &body_size.virtual_size) < 0 ||
-		    mail_get_physical_size(mail, &body_size.physical_size) < 0)
-			return -1;
-		fetch_size = &body_size;
-		ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
-		break;
-	case 'H':
-		/* BODY[HEADER] - fetch only header */
-		if (mail_get_hdr_stream(mail, &hdr_size, &input) < 0)
-			return -1;
-                fetch_size = &hdr_size;
-		ctx->cur_size_field = MAIL_FETCH_MESSAGE_PARTS;
-		break;
-	case 'T':
-		/* BODY[TEXT] - skip header */
-		if (mail_get_stream(mail, &hdr_size, &body_size, &input) < 0)
-			return -1;
-		i_stream_skip(input, hdr_size.physical_size);
-                fetch_size = &body_size;
-		ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
-		break;
-	default:
-		i_unreached();
-	}
-
-	ctx->cur_input = input;
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = TRUE;
-
-	return fetch_data(ctx, body, fetch_size);
-}
-
-static int fetch_header_partial_from(struct imap_fetch_context *ctx,
-				     const struct imap_fetch_body_data *body,
-				     const char *header_section)
-{
-	struct message_size msg_size;
-	struct istream *input;
-	uoff_t old_offset;
-
-	/* MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-
-	if (strncmp(header_section, "HEADER.FIELDS ", 14) == 0) {
-		input = i_stream_create_header_filter(ctx->cur_input,
-					HEADER_FILTER_INCLUDE,
-					body->fields, body->fields_count,
-					null_header_filter_callback, NULL);
-	} else if (strncmp(header_section, "HEADER.FIELDS.NOT ", 18) == 0) {
-		input = i_stream_create_header_filter(ctx->cur_input,
-					HEADER_FILTER_EXCLUDE,
-					body->fields, body->fields_count,
-					null_header_filter_callback, 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;
-
-	old_offset = ctx->cur_input->v_offset;
-	if (message_get_header_size(ctx->cur_input, &msg_size, NULL) < 0) {
-		fetch_read_error(ctx);
-		return -1;
-	}
-	i_stream_seek(ctx->cur_input, old_offset);
-
-	ctx->cur_size_field = 0;
-	return fetch_data(ctx, body, &msg_size);
-}
-
-static int
-fetch_body_header_partial(struct imap_fetch_context *ctx, struct mail *mail,
-			  const struct imap_fetch_body_data *body)
-{
-	if (mail_get_hdr_stream(mail, NULL, &ctx->cur_input) < 0)
-		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,
-			 struct imap_fetch_body_data *body)
-{
-	struct message_size size;
-	uoff_t old_offset;
-
-	if (mail == NULL) {
-		/* deinit */
-		mailbox_header_lookup_unref(&body->header_ctx);
-		return 0;
-	}
-
-	if (mail_get_header_stream(mail, body->header_ctx, &ctx->cur_input) < 0)
-		return -1;
-
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = FALSE;
-
-	old_offset = ctx->cur_input->v_offset;
-	if (message_get_body_size(ctx->cur_input, &size, NULL) < 0) {
-		fetch_read_error(ctx);
-		return -1;
-	}
-	i_stream_seek(ctx->cur_input, old_offset);
-
-	/* FIXME: We'll just always add the end of headers line now.
-	   ideally mail-storage would have a way to tell us if it exists. */
-	size.virtual_size += 2;
-	ctx->cur_append_eoh = TRUE;
-
-	ctx->cur_size_field = 0;
-	return fetch_data(ctx, body, &size);
-}
-
-static int fetch_body_mime(struct imap_fetch_context *ctx, struct mail *mail,
-			   const struct imap_fetch_body_data *body)
-{
-	const struct message_part *part;
-	const char *section;
-
-	if (imap_msgpart_find(mail, body->section, &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;
-	}
-
-	if (mail_get_stream(mail, NULL, NULL, &ctx->cur_input) < 0)
-		return -1;
-
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = TRUE;
-	ctx->cur_size_field = MAIL_FETCH_MESSAGE_PARTS;
-
-	if (*section == '\0') {
-		/* fetch the whole section */
-		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, "MIME") == 0) {
-		/* fetch section's MIME header */
-		i_stream_seek(ctx->cur_input, part->physical_pos);
-		return fetch_data(ctx, body, &part->header_size);
-	}
-
-	/* TEXT and HEADER are only for message/rfc822 parts */
-	if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) {
-		string_t *str = get_prefix(ctx, body, 0);
-		if (o_stream_send(ctx->client->output,
-				  str_data(str), str_len(str)) < 0)
-			return -1;
-		return 1;
-	}
-
-	i_assert(part->children != NULL && part->children->next == NULL);
-	part = part->children;
-
-	if (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 */
-		i_stream_seek(ctx->cur_input, part->physical_pos);
-		return fetch_data(ctx, body, &part->header_size);
-	}
-
-	if (strncmp(section, "HEADER", 6) == 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 bool fetch_body_header_fields_check(const char *section)
-{
-	if (*section++ != '(')
-		return FALSE;
-
-	if (*section == ')')
-		return FALSE; /* has to be at least one field */
-
-	while (*section != '\0' && *section != ')') {
-		if (*section == '(')
-			return FALSE;
-		section++;
-	}
-
-	if (*section++ != ')')
-		return FALSE;
-
-	if (*section != '\0')
-		return FALSE;
-	return TRUE;
-}
-
-static bool fetch_body_header_fields_init(struct imap_fetch_init_context *ctx,
-					  struct imap_fetch_body_data *body,
-					  const char *section)
-{
-	const char *const *arr;
-
-	if (!fetch_body_header_fields_check(section))
-		return FALSE;
-
-	if ((ctx->fetch_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, 0, "NIL",
-				       fetch_body_header_partial, body);
-		return TRUE;
-	}
-
-	for (arr = body->fields; *arr != NULL; arr++) {
-		const char *hdr = p_strdup(ctx->fetch_ctx->pool, *arr);
-		array_append(&ctx->fetch_ctx->all_headers, &hdr, 1);
-	}
-
-	body->header_ctx = mailbox_header_lookup_init(ctx->fetch_ctx->box,
-						      body->fields);
-	imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT, "NIL",
-			       fetch_body_header_fields, body);
-	return TRUE;
-}
-
-static bool
-fetch_body_section_name_init(struct imap_fetch_init_context *ctx,
-			     struct imap_fetch_body_data *body)
-{
-	const char *section = body->section;
-
-	if (*section == '\0') {
-		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
-			MAIL_FETCH_STREAM_BODY;
-		imap_fetch_add_handler(ctx, 0, "NIL", fetch_body, body);
-		return TRUE;
-	}
-
-	if (strcmp(section, "TEXT") == 0) {
-		ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
-		imap_fetch_add_handler(ctx, 0, "NIL", fetch_body, body);
-		return TRUE;
-	}
-
-	if (strncmp(section, "HEADER", 6) == 0) {
-		/* exact header matches could be cached */
-		if (section[6] == '\0') {
-			ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
-			imap_fetch_add_handler(ctx, 0, "NIL", 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, 0, "NIL",
-					       fetch_body_header_partial, body);
-			return TRUE;
-		}
-	} else if (*section >= '0' && *section <= '9') {
-		ctx->fetch_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, 0, "NIL",
-					       fetch_body_mime, body);
-			return TRUE;
-		}
-	}
-
-	ctx->error = "Invalid BODY[..] parameter: Unknown or broken section";
-	return FALSE;
-}
-
-/* Parse next digits in string into integer. Returns FALSE if the integer
+/* Parse next digits in string into integer. Returns -1 if the integer
    becomes too big and wraps. */
-static bool read_uoff_t(const char **p, uoff_t *value)
+static int read_uoff_t(const char **p, uoff_t *value)
 {
 	uoff_t prev;
 
@@ -662,70 +144,104 @@
 		*value = *value * 10 + (**p - '0');
 
 		if (*value < prev)
-			return FALSE;
+			return -1;
 
 		(*p)++;
 	}
-
-	return TRUE;
+	return 0;
 }
 
-static bool
-body_section_build(struct imap_fetch_init_context *ctx,
-		   struct imap_fetch_body_data *body, const char *prefix,
-		   const struct imap_arg *args, unsigned int args_count)
+static int
+body_header_fields_parse(struct imap_fetch_init_context *ctx,
+			 struct imap_fetch_body_data *body, const char *prefix,
+			 const struct imap_arg *args, unsigned int args_count)
 {
 	string_t *str;
-	const char **arr, *value;
+	const char *value;
 	size_t i;
 
 	str = str_new(ctx->fetch_ctx->pool, 128);
 	str_append(str, prefix);
 	str_append(str, " (");
 
-	/* @UNSAFE: NULL-terminated list of headers */
-	arr = p_new(ctx->fetch_ctx->pool, const char *, args_count + 1);
-
 	for (i = 0; i < args_count; i++) {
 		if (!imap_arg_get_astring(&args[i], &value)) {
 			ctx->error = "Invalid BODY[..] parameter: "
 				"Header list contains non-strings";
-			return FALSE;
+			return -1;
 		}
+		value = t_str_ucase(value);
 
 		if (i != 0)
 			str_append_c(str, ' ');
-		arr[i] = p_strdup(ctx->fetch_ctx->pool, t_str_ucase(value));
 
 		if (args[i].type == IMAP_ARG_ATOM)
-			str_append(str, arr[i]);
+			str_append(str, value);
 		else {
 			str_append_c(str, '"');
-			str_append(str, str_escape(arr[i]));
+			imap_dquote_append(str, value);
 			str_append_c(str, '"');
 		}
 	}
 	str_append_c(str, ')');
+	body->section = str_c(str);
+	return 0;
+}
 
-	qsort(arr, args_count, sizeof(*arr), i_strcasecmp_p);
-	body->fields = arr;
-	body->fields_count = args_count;
-	body->section = str_c(str);
-	return TRUE;
+static int body_parse_partial(struct imap_fetch_body_data *body,
+			      const char *p, const char **error_r)
+{
+	uoff_t offset, size = (uoff_t)-1;
+
+	if (*p == '\0')
+		return 0;
+	/* <start.end> */
+	if (*p != '<') {
+		*error_r = "Unexpected data after ']'";
+		return -1;
+	}
+
+	p++;
+	body->partial = TRUE;
+
+	if (read_uoff_t(&p, &offset) < 0 || offset > OFF_T_MAX) {
+		/* wrapped */
+		*error_r = "Too big partial start";
+		return -1;
+	}
+
+	if (*p == '.') {
+		p++;
+		if (read_uoff_t(&p, &size) < 0 || size > OFF_T_MAX) {
+			/* wrapped */
+			*error_r = "Too big partial end";
+			return -1;
+		}
+	}
+
+	if (*p != '>') {
+		*error_r = "Missing '>' in partial";
+		return -1;
+	}
+	if (p[1] != '\0') {
+		*error_r = "Unexpected data after '>'";
+		return -1;
+	}
+	imap_msgpart_set_partial(body->msgpart, offset, size);
+	return 0;
 }
-  
+
 bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx)
 {
 	struct imap_fetch_body_data *body;
 	const struct imap_arg *list_args;
 	unsigned int list_count;
-	const char *partial, *str, *p;
+	const char *str, *p, *error;
 
 	i_assert(strncmp(ctx->name, "BODY", 4) == 0);
 	p = ctx->name + 4;
 
 	body = p_new(ctx->fetch_ctx->pool, struct imap_fetch_body_data, 1);
-	body->max_size = (uoff_t)-1;
 
 	if (strncmp(p, ".PEEK", 5) == 0) {
 		body->peek = TRUE;
@@ -746,9 +262,10 @@
 			ctx->error = "Invalid BODY[..] parameter: Missing ']'";
 			return FALSE;
 		}
-		if (!body_section_build(ctx, body, p+1, list_args, list_count))
+		if (body_header_fields_parse(ctx, body, p+1,
+					     list_args, list_count) < 0)
 			return FALSE;
-		p = str;
+		p = str+1;
 		ctx->args += 2;
 	} else {
 		/* no headers list */
@@ -760,43 +277,26 @@
 		}
 		body->section = p_strdup_until(ctx->fetch_ctx->pool,
 					       body->section, p);
+		p++;
+	}
+	if (imap_msgpart_parse(ctx->fetch_ctx->box, body->section,
+			       &body->msgpart) < 0) {
+		ctx->error = "Invalid BODY[..] section";
+		return -1;
+	}
+	ctx->fetch_ctx->fetch_data |=
+		imap_msgpart_get_fetch_data(body->msgpart);
+
+	if (body_parse_partial(body, p, &error) < 0) {
+		ctx->error = p_strdup_printf(ctx->fetch_ctx->pool,
+			"Invalid BODY[..] parameter: %s", error);
+		return FALSE;
 	}
 
-	if (*++p == '<') {
-		/* <start.end> */
-		partial = p;
-		p++;
-		body->skip_set = TRUE;
-
-		if (!read_uoff_t(&p, &body->skip) || body->skip > OFF_T_MAX) {
-			/* wrapped */
-			ctx->error = "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 */
-				ctx->error = "Invalid BODY[..] parameter: "
-					"Too big partial end";
-				return FALSE;
-			}
-		}
-
-		if (*p != '>') {
-			ctx->error = t_strdup_printf(
-				"Invalid BODY[..] parameter: "
-				"Missing '>' in '%s'", partial);
-			return FALSE;
-		}
-	}
-
-	/* sanitize the name */
-	ctx->name = get_body_name(body);
-	return fetch_body_section_name_init(ctx, body);
+	/* update the section name for the imap_fetch_add_handler() */
+	ctx->name = p_strdup(ctx->fetch_ctx->pool, get_body_name(body));
+	imap_fetch_add_handler(ctx, 0, "NIL", fetch_body_msgpart, body);
+	return TRUE;
 }
 
 static int fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail,
@@ -811,52 +311,55 @@
 	return 1;
 }
 
+static int
+fetch_and_free_msgpart(struct imap_fetch_context *ctx,
+		       struct mail *mail, struct imap_msgpart **_msgpart)
+{
+	struct imap_msgpart_open_result result;
+	int ret;
+
+	ret = imap_msgpart_open(mail, *_msgpart, &result);
+	imap_msgpart_free(_msgpart);
+	if (ret < 0)
+		return -1;
+	ctx->cur_input = result.input;
+	ctx->cur_size = result.size;
+	ctx->cur_size_field = result.size_field;
+	ctx->cont_handler = fetch_stream_continue;
+	return 0;
+}
+
 static int fetch_rfc822(struct imap_fetch_context *ctx, struct mail *mail,
 			void *context ATTR_UNUSED)
 {
-	struct message_size size;
+	struct imap_msgpart *msgpart;
 	const char *str;
-	struct istream *input;
+
+	msgpart = imap_msgpart_full();
+	fetch_and_free_msgpart(ctx, mail, &msgpart);
 
-	if (mail_get_stream(mail, NULL, NULL, &input) < 0 ||
-	    mail_get_virtual_size(mail, &size.virtual_size) < 0 ||
-	    mail_get_physical_size(mail, &size.physical_size) < 0)
+	str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n", ctx->cur_size);
+	if (ctx->first) {
+		str++; ctx->first = FALSE;
+	}
+	if (o_stream_send_str(ctx->client->output, str) < 0)
 		return -1;
 
-	ctx->cur_input = input;
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = FALSE;
-
-	if (ctx->cur_offset == 0) {
-		str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n",
-				      size.virtual_size);
-		if (ctx->first) {
-			str++; ctx->first = FALSE;
-		}
-		if (o_stream_send_str(ctx->client->output, str) < 0)
-			return -1;
-	}
-
 	ctx->cur_name = "RFC822";
-        ctx->cur_size = size.virtual_size;
-	ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
-	return fetch_stream(ctx, &size);
+	return ctx->cont_handler(ctx);
 }
 
 static int fetch_rfc822_header(struct imap_fetch_context *ctx,
 			       struct mail *mail, void *context ATTR_UNUSED)
 {
-	struct message_size hdr_size;
+	struct imap_msgpart *msgpart;
 	const char *str;
 
-	if (mail_get_hdr_stream(mail, &hdr_size, &ctx->cur_input) < 0)
-		return -1;
-
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = FALSE;
+	msgpart = imap_msgpart_header();
+	fetch_and_free_msgpart(ctx, mail, &msgpart);
 
 	str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n",
-			      hdr_size.virtual_size);
+			      ctx->cur_size);
 	if (ctx->first) {
 		str++; ctx->first = FALSE;
 	}
@@ -864,36 +367,28 @@
 		return -1;
 
 	ctx->cur_name = "RFC822.HEADER";
-        ctx->cur_size = hdr_size.virtual_size;
-	ctx->cur_size_field = MAIL_FETCH_MESSAGE_PARTS;
-	return fetch_stream(ctx, &hdr_size);
+	return ctx->cont_handler(ctx);
 }
 
 static int fetch_rfc822_text(struct imap_fetch_context *ctx, struct mail *mail,
 			     void *context ATTR_UNUSED)
 {
-	struct message_size hdr_size, body_size;
+	struct imap_msgpart *msgpart;
 	const char *str;
 
-	if (mail_get_stream(mail, &hdr_size, &body_size, &ctx->cur_input) < 0)
-		return -1;
-
-	i_stream_ref(ctx->cur_input);
-	ctx->update_partial = FALSE;
+	msgpart = imap_msgpart_body();
+	fetch_and_free_msgpart(ctx, mail, &msgpart);
 
 	str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n",
-			      body_size.virtual_size);
+			      ctx->cur_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_name = "RFC822.TEXT";
-        ctx->cur_size = body_size.virtual_size;
-	ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
-	return fetch_stream(ctx, &body_size);
+	return ctx->cont_handler(ctx);
 }
 
 bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx)
--- a/src/imap/imap-fetch.c	Thu Jun 21 21:50:35 2012 +0300
+++ b/src/imap/imap-fetch.c	Thu Jun 21 21:52:56 2012 +0300
@@ -362,7 +362,6 @@
 	ctx->trans = mailbox_transaction_begin(ctx->box,
 		MAILBOX_TRANSACTION_FLAG_HIDE |
 		MAILBOX_TRANSACTION_FLAG_REFRESH);
-	ctx->select_counter = ctx->client->select_counter;
 
 	/* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
 	mail_search_args_init(ctx->search_args, ctx->box, TRUE,
@@ -435,7 +434,7 @@
 
 			if (ctx->cur_mail->expunged) {
 				/* not an error, just lost it. */
-				ctx->partial_fetch = TRUE;
+				ctx->skipped_expunged_msgs = TRUE;
 				if (imap_fetch_send_nil_reply(ctx) < 0)
 					return -1;
 			} else {
@@ -498,7 +497,7 @@
 			if (ret < 0) {
 				if (ctx->cur_mail->expunged) {
 					/* not an error, just lost it. */
-					ctx->partial_fetch = TRUE;
+					ctx->skipped_expunged_msgs = TRUE;
 					if (imap_fetch_send_nil_reply(ctx) < 0)
 						return -1;
 				} else {
--- a/src/imap/imap-fetch.h	Thu Jun 21 21:50:35 2012 +0300
+++ b/src/imap/imap-fetch.h	Thu Jun 21 21:52:56 2012 +0300
@@ -69,18 +69,15 @@
 	const ARRAY_TYPE(uint32_t) *qresync_sample_uidset;
 
 	ARRAY_TYPE(keywords) tmp_keywords;
-	unsigned int select_counter;
 
 	unsigned int flags_have_handler:1;
 	unsigned int flags_update_seen:1;
 	unsigned int seen_flags_changed:1;
 	unsigned int flags_show_only_seen_changes:1;
-	unsigned int update_partial:1;
-	unsigned int cur_append_eoh:1;
 	unsigned int first:1;
 	unsigned int line_partial:1;
 	unsigned int line_finished:1;
-	unsigned int partial_fetch:1;
+	unsigned int skipped_expunged_msgs:1;
 	unsigned int send_vanished:1;
 	unsigned int failed:1;
 };