changeset 14622:6fb61872b30a

lib-imap-storage: imap-msgpart rewrite and API change. The new API allows first parsing the validity of section strings and later relying on them being valid without having to re-parse it. The implementation also fixes a few things and adds "partial fetch cache".
author Timo Sirainen <tss@iki.fi>
date Thu, 21 Jun 2012 21:50:35 +0300
parents bf8a885c2077
children df8ba29d9eb3
files src/lib-imap-storage/Makefile.am src/lib-imap-storage/imap-msgpart-url.c src/lib-imap-storage/imap-msgpart.c src/lib-imap-storage/imap-msgpart.h src/lib-storage/mail-storage-private.h
diffstat 5 files changed, 532 insertions(+), 284 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-imap-storage/Makefile.am	Thu Jun 21 21:47:06 2012 +0300
+++ b/src/lib-imap-storage/Makefile.am	Thu Jun 21 21:50:35 2012 +0300
@@ -5,6 +5,7 @@
 	-I$(top_srcdir)/src/lib-test \
 	-I$(top_srcdir)/src/lib-charset \
 	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/lib-storage \
 	-I$(top_srcdir)/src/lib-imap
 
--- a/src/lib-imap-storage/imap-msgpart-url.c	Thu Jun 21 21:47:06 2012 +0300
+++ b/src/lib-imap-storage/imap-msgpart-url.c	Thu Jun 21 21:50:35 2012 +0300
@@ -1,3 +1,5 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
 #include "lib.h"
 #include "network.h"
 #include "istream.h"
@@ -162,13 +164,32 @@
 	return 1;
 }
 
+static int
+imap_msgpart_url_open_part(struct imap_msgpart_url *mpurl, struct mail **mail_r,
+			   struct imap_msgpart **msgpart_r, const char **error_r)
+{
+	int ret;
+
+	if ((ret = imap_msgpart_url_open_mail(mpurl, mail_r, error_r)) <= 0)
+		return ret;
+
+	if (imap_msgpart_parse((*mail_r)->box, mpurl->section, msgpart_r) < 0) {
+		*error_r = "Invalid section";
+		return 0;
+	}
+	imap_msgpart_set_partial(*msgpart_r, mpurl->partial_offset,
+				 mpurl->partial_size == 0 ? (uoff_t)-1 :
+				 mpurl->partial_size);
+	return 1;
+}
+
 int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl,
 			       struct istream **stream_r, uoff_t *size_r,
 			       const char **error_r)
 {
 	struct mail *mail;
-	struct istream *input;
-	uoff_t part_size;
+	struct imap_msgpart *msgpart;
+	struct imap_msgpart_open_result result;
 	int ret;
 
 	if (mpurl->input != NULL) {
@@ -179,20 +200,20 @@
 	}
 
 	/* open mail if it is not yet open */
-	if ((ret = imap_msgpart_url_open_mail(mpurl, &mail, error_r)) <= 0)
+	ret = imap_msgpart_url_open_part(mpurl, &mail, &msgpart, error_r);
+	if (ret <= 0)
 		return ret;
 
 	/* open the referenced part as a stream */
-	if ((ret = imap_msgpart_open(mail, mpurl->section,
-				     mpurl->partial_offset, mpurl->partial_size,
-				     &input, &part_size, error_r)) <= 0)
+	ret = imap_msgpart_open(mail, msgpart, &result);
+	imap_msgpart_free(&msgpart);
+	if (ret < 0) {
+		*error_r = mailbox_get_last_error(mail->box, NULL);
 		return ret;
+	}
 
-	mpurl->input = input;
-	mpurl->part_size = part_size;
-
-	*stream_r = input;
-	*size_r = part_size;
+	*stream_r = mpurl->input = result.input;
+	*size_r = mpurl->part_size = result.size;
 	return 1;
 }
 
@@ -200,17 +221,17 @@
 			    const char **error_r)
 {
 	struct mail *mail;
+	struct imap_msgpart *msgpart;
 	int ret;
 
 	if (mpurl->input != NULL)
 		return 1;
 
 	/* open mail if it is not yet open */
-	if ((ret = imap_msgpart_url_open_mail(mpurl, &mail, error_r)) <= 0)
-		return ret;
-
-	/* open the referenced part as a stream */
-	return imap_msgpart_verify(mail, mpurl->section, error_r);
+	ret = imap_msgpart_url_open_part(mpurl, &mail, &msgpart, error_r);
+	if (ret > 0)
+		imap_msgpart_free(&msgpart);
+	return ret;
 }
 
 void imap_msgpart_url_free(struct imap_msgpart_url **_mpurl)
--- a/src/lib-imap-storage/imap-msgpart.c	Thu Jun 21 21:47:06 2012 +0300
+++ b/src/lib-imap-storage/imap-msgpart.c	Thu Jun 21 21:50:35 2012 +0300
@@ -1,25 +1,89 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
 #include "lib.h"
 #include "array.h"
 #include "istream.h"
 #include "istream-crlf.h"
+#include "istream-nonuls.h"
 #include "istream-header-filter.h"
 #include "message-parser.h"
-#include "mail-storage.h"
+#include "mail-storage-private.h"
 #include "mail-namespace.h"
 #include "imap-parser.h"
 #include "imap-msgpart.h"
 
-int imap_msgpart_find(struct mail *mail, const char *section,
-		      const struct message_part **part_r,
-		      const char **subsection_r)
+enum fetch_type {
+	FETCH_FULL,
+	FETCH_MIME,
+	FETCH_HEADER,
+	FETCH_HEADER_FIELDS,
+	FETCH_HEADER_FIELDS_NOT,
+	FETCH_BODY
+};
+
+struct imap_msgpart {
+	pool_t pool;
+
+	/* "" for root, otherwise e.g. "1.2.3". the .MIME, .HEADER, etc.
+	   suffix not included */
+	const char *section_number;
+	enum fetch_type fetch_type;
+	enum mail_fetch_field wanted_fields;
+
+	/* HEADER.FIELDS[.NOT] (list of headers) */
+        struct mailbox_header_lookup_ctx *header_ctx;
+	const char *const *headers;
+
+	/* which part of the message part to fetch (default: 0..(uoff_t)-1) */
+	uoff_t partial_offset, partial_size;
+};
+
+struct imap_msgpart_open_ctx {
+	/* from matching message_part, set after opening: */
+	uoff_t physical_pos;
+	struct message_size mime_hdr_size;
+	struct message_size mime_body_size;
+};
+
+static struct imap_msgpart *imap_msgpart_type(enum fetch_type fetch_type)
 {
-	struct message_part *part;
+	struct imap_msgpart *msgpart;
+	pool_t pool;
+
+	pool = pool_alloconly_create("imap msgpart", sizeof(*msgpart)+32);
+	msgpart = p_new(pool, struct imap_msgpart, 1);
+	msgpart->pool = pool;
+	msgpart->partial_size = (uoff_t)-1;
+	msgpart->fetch_type = fetch_type;
+	if (fetch_type == FETCH_HEADER || fetch_type == FETCH_FULL)
+		msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+	if (fetch_type == FETCH_BODY || fetch_type == FETCH_FULL)
+		msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+	return msgpart;
+}
+
+struct imap_msgpart *imap_msgpart_full(void)
+{
+	return imap_msgpart_type(FETCH_FULL);
+}
+
+struct imap_msgpart *imap_msgpart_header(void)
+{
+	return imap_msgpart_type(FETCH_HEADER);
+}
+
+struct imap_msgpart *imap_msgpart_body(void)
+{
+	return imap_msgpart_type(FETCH_BODY);
+}
+
+static struct message_part *
+imap_msgpart_find(struct message_part *parts, const char *section)
+{
+	struct message_part *part = parts;
 	const char *path;
 	unsigned int num;
 
-	if (mail_get_parts(mail, &part) < 0)
-		return -1;
-
 	path = section;
 	while (*path >= '0' && *path <= '9' && part != NULL) {
 		/* get part number, we have already verified its validity */
@@ -53,21 +117,18 @@
 			part = part->children;
 		}
 	}
-
-	*part_r = part;
-	*subsection_r = path;
-	return 0;
+	i_assert(part == NULL || *path == '\0');
+	return part;
 }
 
 static int
-imap_msgpart_get_header_fields(const char *header_list,
-			       const char *const **fields_r, size_t *count_r)
+imap_msgpart_get_header_fields(pool_t pool, const char *header_list,
+			       ARRAY_TYPE(const_string) *fields)
 {
 	struct istream *input;
 	struct imap_parser *parser;
 	const struct imap_arg *args, *hdr_list;
 	unsigned int list_count;
-	ARRAY_TYPE(const_string) fields = ARRAY_INIT;
 	unsigned int i;
 	int result = 0;
 
@@ -76,304 +137,452 @@
 
 	if (imap_parser_finish_line(parser, 0, 0, &args) > 0 &&
 	    imap_arg_get_list_full(args, &hdr_list, &list_count) &&
+	    args[1].type == IMAP_ARG_EOL &&
 	    list_count > 0) {
 		const char *value;
 		
-		if (fields_r != NULL)
-			t_array_init(&fields, list_count);
-
+		p_array_init(fields, pool, list_count);
 		for (i = 0; i < list_count; i++) {
 			if (!imap_arg_get_astring(&hdr_list[i], &value)) {
 				result = -1;
 				break;
 			}
 
-			if (fields_r != NULL) {
-				value = t_str_ucase(value);
-				array_append(&fields, &value, 1);
-			}
-		}
-
-		if (fields_r != NULL) {
-			*fields_r = array_get(&fields, &list_count);
-			*count_r = list_count;
+			value = p_strdup(pool, t_str_ucase(value));
+			array_append(fields, &value, 1);
 		}
 	} else {
 		result = -1;
 	}
 
+	/* istream-header-filter requires headers to be sorted */
+	array_sort(fields, i_strcasecmp_p);
+
 	imap_parser_unref(&parser);
 	i_stream_unref(&input);
 	return result;
 }
 
 static int
-imap_msgpart_verify_header_fields(const char *header_list, const char **error_r)
+imap_msgpart_parse_header_fields(struct mailbox *box,
+				 struct imap_msgpart *msgpart,
+				 const char *header_list)
 {
+	ARRAY_TYPE(const_string) fields;
+
 	/* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-	if (imap_msgpart_get_header_fields(header_list, NULL, NULL) < 0) {
-		*error_r = "Invalid header fields";
-		return 0;
-	}
-	return 1;
+	if (imap_msgpart_get_header_fields(msgpart->pool, header_list,
+					   &fields) < 0)
+		return -1;
+
+	(void)array_append_space(&fields);
+	msgpart->headers = array_idx(&fields, 0);
+	msgpart->header_ctx = mailbox_header_lookup_init(box, msgpart->headers);
+	return 0;
 }
 
-static struct istream *
-imap_msgpart_get_partial_header(struct istream *mail_input, bool exclude,
-				const char *header_list,
-				struct message_size *hdr_size_r,
-				const char **error_r)
+int imap_msgpart_parse(struct mailbox *box, const char *section,
+		       struct imap_msgpart **msgpart_r)
 {
-	const char *const *hdr_fields;
-	size_t hdr_count;
-	struct istream *input;
-	uoff_t old_offset;
+	struct imap_msgpart *msgpart;
+	pool_t pool;
+	unsigned int i;
+	bool next_digit;
+	int ret;
+
+	pool = pool_alloconly_create("imap msgpart", 512);
+	msgpart = *msgpart_r = p_new(pool, struct imap_msgpart, 1);
+	msgpart->pool = pool;
+	msgpart->partial_size = (uoff_t)-1;
 
-	/* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-	if (imap_msgpart_get_header_fields(header_list,
-					   &hdr_fields, &hdr_count) < 0) {
-		*error_r = "Invalid header fields";
-		return NULL;
+	/* get the section number */
+	next_digit = TRUE;
+	for (i = 0; section[i] != '\0'; i++) {
+		if (section[i] >= '0' && section[i] <= '9') {
+			next_digit = FALSE;
+		} else if (!next_digit && section[i] == '.') {
+			next_digit = TRUE;
+		} else {
+			break;
+		}
+	}
+	if (i == 0) {
+		/* [], [HEADER], etc. */
+		msgpart->section_number = "";
+	} else if (section[i] == '\0') {
+		/* [1.2.3] */
+		if (i > 0 && section[i-1] == '.') {
+			pool_unref(&pool);
+			return -1;
+		}
+		msgpart->section_number = p_strdup(pool, section);
+		section = "";
+	} else {
+		/* [1.2.3.MIME], [1.2.3.HEADER], etc */
+		if (section[i-1] != '.') {
+			pool_unref(&pool);
+			return -1;
+		}
+		msgpart->section_number = p_strndup(pool, section, i-1);
+		section += i;
 	}
 
-	if (!exclude) {
+	if (*section == '\0') {
+		/* full message/MIME part */
+		msgpart->fetch_type = FETCH_FULL;
+		msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+		if (*msgpart->section_number == '\0')
+			msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+		return 0;
+	}
+	section = t_str_ucase(section);
+
+	if (strcmp(section, "MIME") == 0) {
+		msgpart->fetch_type = FETCH_MIME;
+		msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+	} else if (strcmp(section, "TEXT") == 0) {
+		/* message body */
+		msgpart->fetch_type = FETCH_BODY;
+		msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+	} else if (strncmp(section, "HEADER", 6) == 0) {
+		/* header */
+		if (section[6] == '\0') {
+			msgpart->fetch_type = FETCH_HEADER;
+			ret = 0;
+		} else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
+			msgpart->fetch_type = FETCH_HEADER_FIELDS;
+			ret = imap_msgpart_parse_header_fields(box, msgpart,
+							       section+14);
+		} else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
+			msgpart->fetch_type = FETCH_HEADER_FIELDS_NOT;
+			ret = imap_msgpart_parse_header_fields(box, msgpart,
+							       section+18);
+		} else {
+			ret = -1;
+		}
+		if (ret < 0) {
+			imap_msgpart_free(&msgpart);
+			return -1;
+		}
+		if (msgpart->fetch_type == FETCH_HEADER_FIELDS) {
+			/* we may be able to get this from cache, don't give a
+			   wanted_fields hint */
+		} else if (*msgpart->section_number == '\0')
+			msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+		else
+			msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+	}
+	return 0;
+}
+
+void imap_msgpart_free(struct imap_msgpart **_msgpart)
+{
+	struct imap_msgpart *msgpart = *_msgpart;
+
+	*_msgpart = NULL;
+
+	if (msgpart->header_ctx != NULL)
+		mailbox_header_lookup_unref(&msgpart->header_ctx);
+	pool_unref(&msgpart->pool);
+}
+
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+			      uoff_t offset, uoff_t size)
+{
+	msgpart->partial_offset = offset;
+	msgpart->partial_size = size;
+}
+
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart)
+{
+	return msgpart->partial_offset;
+}
+
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart)
+{
+	return msgpart->wanted_fields;
+}
+
+static int
+imap_msgpart_get_partial_header(struct mail *mail, struct istream *mail_input,
+				const struct imap_msgpart *msgpart,
+				struct message_size *hdr_size_r,
+				struct imap_msgpart_open_result *result_r)
+{
+	const char *const *hdr_fields = msgpart->headers;
+	unsigned int hdr_count = str_array_length(hdr_fields);
+	struct istream *input;
+
+	if (msgpart->fetch_type == FETCH_HEADER_FIELDS) {
 		input = i_stream_create_header_filter(mail_input,
-						      HEADER_FILTER_INCLUDE,
+						      HEADER_FILTER_INCLUDE |
+						      HEADER_FILTER_HIDE_BODY,
 						      hdr_fields, hdr_count,
 						      null_header_filter_callback, NULL);
 	} else {
+		i_assert(msgpart->fetch_type == FETCH_HEADER_FIELDS_NOT);
 		input = i_stream_create_header_filter(mail_input,
-						      HEADER_FILTER_EXCLUDE,
+						      HEADER_FILTER_EXCLUDE |
+						      HEADER_FILTER_HIDE_BODY,
 						      hdr_fields, hdr_count,
 						      null_header_filter_callback, NULL);
 	}
 
-	old_offset = input->v_offset;
 	if (message_get_header_size(input, hdr_size_r, NULL) < 0) {
-		*error_r = "Failed to determine header size";
-		return NULL;
+		errno = input->stream_errno;
+		mail_storage_set_critical(mail->box->storage,
+			"read(%s) failed: %m", i_stream_get_name(mail_input));
+		i_stream_unref(&input);
+		return -1;
+	}
+	i_stream_seek(input, 0);
+	result_r->input = input;
+	result_r->size = hdr_size_r->virtual_size;
+	return 0;
+}
+
+static void
+skip_using_parts(struct mail *mail, struct istream *input,
+		 uoff_t *virtual_skip)
+{
+	enum mail_lookup_abort old_lookup_abort;
+	struct message_part *parts, *part;
+	uoff_t vpos;
+	int ret;
+
+	old_lookup_abort = mail->lookup_abort;
+	mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE;
+	ret = mail_get_parts(mail, &parts);
+	mail->lookup_abort = old_lookup_abort;
+	if (ret < 0)
+		return;
+
+	for (part = parts, vpos = 0; part != NULL; ) {
+		if (vpos + part->header_size.virtual_size > *virtual_skip)
+			break;
+		/* skip header */
+		vpos += part->header_size.virtual_size;
+		*virtual_skip -= part->header_size.virtual_size;
+		i_stream_seek(input, part->physical_pos +
+			      part->header_size.physical_size);
+
+		if (vpos + part->body_size.virtual_size <= *virtual_skip) {
+			/* skip body */
+			vpos += part->body_size.virtual_size;
+			*virtual_skip -= part->body_size.virtual_size;
+			i_stream_seek(input, part->physical_pos +
+				      part->header_size.physical_size +
+				      part->body_size.physical_size);
+			part = part->next;
+		} else {
+			/* maybe we have a child and can skip using it? */
+			part = part->children;
+		}
 	}
-	i_stream_seek(input, old_offset);
+}
+
+static struct istream *
+imap_msgpart_crlf_seek(struct mail *mail, struct istream *input,
+		       const struct imap_msgpart *msgpart)
+{
+	struct mail_msgpart_partial_cache *cache = &mail->box->partial_cache;
+	struct istream *nul_input, *crlf_input;
+	const unsigned char *data;
+	size_t size;
+	uoff_t physical_start = input->v_offset;
+	uoff_t virtual_skip = msgpart->partial_offset;
+
+	i_assert(msgpart->headers == NULL); /* HEADER.FIELDS returns CRLFs */
 
+	if (virtual_skip == 0) {
+		/* no need to seek */
+	} else if (cache->uid == mail->uid &&
+		   cache->physical_start == physical_start &&
+		   cache->virtual_pos < virtual_skip) {
+		/* use cache */
+		i_stream_seek(input, cache->physical_pos);
+		virtual_skip -= cache->virtual_pos;
+	} else {
+		/* can't use cache, but maybe we can skip faster using the
+		   message parts. */
+		skip_using_parts(mail, input, &virtual_skip);
+	}
+	if (!mail->has_no_nuls) {
+		nul_input = i_stream_create_nonuls(input, 0x80);
+		i_stream_unref(&input);
+		input = nul_input;
+	}
+	crlf_input = i_stream_create_crlf(input);
+	i_stream_unref(&input);
+	input = crlf_input;
+	i_stream_skip(input, virtual_skip);
+
+	if ((msgpart->partial_offset != 0 ||
+	     msgpart->partial_size != (uoff_t)-1) &&
+	    i_stream_read_data(input, &data, &size, 0) > 0) {
+		/* update cache */
+		cache->uid = mail->uid;
+		cache->physical_start = physical_start;
+		cache->physical_pos = input->v_offset;
+		cache->virtual_pos = msgpart->partial_offset;
+		if (data[0] == '\n') {
+			/* the physical_pos points to virtual CRLF, but
+			   virtual_pos already skipped CR. that can't work,
+			   so seek back the virtual CR */
+			cache->virtual_pos--;
+		}
+	}
 	return input;
 }
 
 static void
-imap_msgpart_get_partial(struct istream **input, struct message_size part_size,
-			 uoff_t partial_offset, uoff_t partial_size,
-			 struct istream **stream_r, uoff_t *size_r)
+imap_msgpart_get_partial(struct mail *mail, const struct imap_msgpart *msgpart,
+			 const struct message_size *part_size,
+			 struct imap_msgpart_open_result *result)
 {
-	struct istream *result;
-	uoff_t size = part_size.virtual_size;
+	struct istream *input2;
+	uoff_t bytes_left;
+
+	/* input is already seeked to the beginning of the wanted data */
 
-	if (partial_offset >= size) {
-		i_stream_unref(input);
-		*size_r = 0;
-		*stream_r = NULL;
+	if (msgpart->partial_offset >= part_size->virtual_size) {
+		/* can't seek past the MIME part */
+		i_stream_unref(&result->input);
+		result->input = i_stream_create_from_data("", 0);
+		result->size = 0;
 		return;
 	}
 
-	if (size != part_size.physical_size) {
-		result = i_stream_create_crlf(*input);
-		i_stream_unref(input);
-		*input = result;
-	}
-
-	if (partial_offset > 0)
-		i_stream_seek(*input, (*input)->v_offset + partial_offset);
-
-	size = partial_size > 0 && (size - partial_offset) > partial_size ?
-		partial_size : (size - partial_offset);
-	result = i_stream_create_limit(*input, size);
-	i_stream_unref(input);
-	
-	*size_r = size;
-	*stream_r = result;
-}
-
-int imap_msgpart_open(struct mail *mail, const char *section,
-		      uoff_t partial_offset, uoff_t partial_size,
-		      struct istream **stream_r,
-		      uoff_t *size_r, const char **error_r)
-{
-	struct message_size hdr_size, body_size, part_size;
-	struct istream *input = NULL;
-
-	/* only get stream when we intend to read actual data */
-	if (stream_r != NULL) {
-		if (mail_get_stream(mail, &hdr_size, &body_size, &input) < 0) {
-			*error_r = "Failed to read message";
-			return -1;
-		}
+	if (part_size->virtual_size == part_size->physical_size) {
+		/* input has CRLF linefeeds, we can quickly seek to
+		   wanted position */
+		i_stream_skip(result->input, msgpart->partial_offset);
+	} else {
+		/* input has LF linefeeds. it can be slow to seek to wanted
+		   position, so try to do caching whenever possible */
+		result->input = imap_msgpart_crlf_seek(mail, result->input,
+						       msgpart);
 	}
 
-	if (section == NULL || *section == '\0') {
-		/* full message */
-		if (stream_r == NULL)
-			return 1;
-
-		part_size.physical_size =
-			hdr_size.physical_size + body_size.physical_size;
-		part_size.virtual_size =
-			hdr_size.virtual_size + body_size.virtual_size;
-
-		i_stream_seek(input, 0);
-		i_stream_ref(input);
-		imap_msgpart_get_partial(&input, part_size,
-					 partial_offset, partial_size,
-					 stream_r, size_r);
-		return 1;
-	}
-
-	section = t_str_ucase(section);
-
-	if (strcmp(section, "TEXT") == 0) {
-		/* message body */
-		if (stream_r == NULL)
-			return 1;
-
-		i_stream_seek(input, hdr_size.physical_size);
-		i_stream_ref(input);
-		imap_msgpart_get_partial(&input, body_size,
-					 partial_offset, partial_size,
-					 stream_r, size_r);
-		return 1;
+	bytes_left = part_size->virtual_size - msgpart->partial_offset;
+	if (msgpart->partial_size <= bytes_left) {
+		/* limit output to specified number of bytes */
+		result->size = msgpart->partial_size;
+	} else {
+		/* send all bytes */
+		result->size = bytes_left;
 	}
+	input2 = i_stream_create_limit(result->input, result->size);
+	i_stream_unref(&result->input);
+	result->input = input2;
+}
 
-	if (strncmp(section, "HEADER", 6) == 0) {
-		/* header */
-		if (stream_r == NULL) {
-			if (section[6] == '\0') {
-				return 1;
-			} else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
-				return imap_msgpart_verify_header_fields(section+14, error_r);
-			} else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-				return imap_msgpart_verify_header_fields(section+18, error_r);
-			}
-		} else {
-			i_stream_seek(input, 0);
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+		      struct imap_msgpart_open_result *result_r)
+{
+	struct message_part *parts, *part = NULL;
+	struct message_size hdr_size, body_size, part_size;
+	struct istream *input = NULL;
+	uoff_t physical_pos = 0;
 
-			if (section[6] == '\0') {
-				i_stream_ref(input);
-			} else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
-				input = imap_msgpart_get_partial_header(input,
-					FALSE, section+14, &hdr_size, error_r);
-			} else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-				input = imap_msgpart_get_partial_header(input,
-					TRUE, section+18, &hdr_size, error_r);
-			} else {
-				input = NULL;
-			}
+	memset(result_r, 0, sizeof(*result_r));
+	memset(&hdr_size, 0, sizeof(hdr_size));
+	memset(&body_size, 0, sizeof(body_size));
+	memset(&part_size, 0, sizeof(part_size));
 
-			if (input != NULL) {
-				imap_msgpart_get_partial(&input, hdr_size,
-					partial_offset, partial_size,
-					stream_r, size_r);
-				return 1;
+	if (*msgpart->section_number != '\0') {
+		/* find the MIME part */
+		if (mail_get_parts(mail, &parts) < 0)
+			return -1;
+		part = imap_msgpart_find(parts, msgpart->section_number);
+		if (part != NULL && (msgpart->fetch_type == FETCH_HEADER ||
+				     msgpart->fetch_type == FETCH_BODY)) {
+			/* fetching message/rfc822 part's header/body */
+			if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0)
+				part = NULL;
+			else {
+				i_assert(part->children != NULL &&
+					 part->children->next == NULL);
+				part = part->children;
 			}
 		}
-	} else if (*section >= '0' && *section <= '9') {
-		const struct message_part *part;
-		const char *subsection;
-
-		if (imap_msgpart_find(mail, section, &part, &subsection) < 0) {
-			*error_r = "Cannot read message part";
-			return -1;
-		}
-
 		if (part == NULL) {
-			*error_r = "Unknown message part";
-			return 0;
-		}
-
-		if (*subsection == '\0') {
-			if (stream_r == NULL)
-				return 1;
-		
-			/* fetch the whole section */
-			i_stream_seek(input, part->physical_pos +
-				      part->header_size.physical_size);
-			i_stream_ref(input);
-			imap_msgpart_get_partial(&input, part->body_size,
-						 partial_offset, partial_size,
-						 stream_r, size_r);
-			return 1;
-		}
-
-		if (strcmp(subsection, "MIME") == 0) {
-			if (stream_r == NULL)
-				return 1;
-
-			/* fetch section's MIME header */
-			i_stream_seek(input, part->physical_pos);
-			i_stream_ref(input);
-			imap_msgpart_get_partial(&input, part->header_size,
-						 partial_offset, partial_size,
-						 stream_r, size_r);
-			return 1;
-		}
-
-		/* TEXT and HEADER are only for message/rfc822 parts */
-		if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) {
-			*error_r = "Invalid section";
+			/* MIME part not found. return an empty stream. */
+			result_r->input = i_stream_create_from_data("", 0);
 			return 0;
 		}
-
-		i_assert(part->children != NULL && part->children->next == NULL);
-		part = part->children;
-
-		if (strcmp(subsection, "TEXT") == 0) {
-			if (stream_r == NULL)
-				return 1;
-
-			/* sub-message body */
-			i_stream_seek(input, part->physical_pos +
-				      part->header_size.physical_size);
-			i_stream_ref(input);
-			imap_msgpart_get_partial(&input, part->body_size,
-						 partial_offset, partial_size,
-						 stream_r, size_r);
-			return 1;
-		}	
+		if (mail_get_stream(mail, NULL, NULL, &input) < 0)
+			return -1;
 
-		if (strncmp(subsection, "HEADER", 6) == 0) {
-			if (stream_r == NULL) {
-				if (section[6] == '\0') {
-					return 1;
-				} else if (strncmp(subsection, "HEADER.FIELDS ", 14) == 0) {
-					return imap_msgpart_verify_header_fields(subsection+14, error_r);
-				} else if (strncmp(subsection, "HEADER.FIELDS.NOT ", 18) == 0) {
-					return imap_msgpart_verify_header_fields(subsection+18, error_r);
-				}
-			} else {
-				i_stream_seek(input, part->physical_pos);
+		physical_pos = part->physical_pos;
+		hdr_size = part->header_size;
+		body_size = part->body_size;
+	} else switch (msgpart->fetch_type) {
+	case FETCH_FULL:
+		/* fetch the whole message */
+		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;
+		result_r->size_field = MAIL_FETCH_VIRTUAL_SIZE;
+		break;
+	case FETCH_MIME:
+		i_unreached();
+	case FETCH_HEADER:
+	case FETCH_HEADER_FIELDS_NOT:
+		/* fetch the message's header */
+		if (mail_get_hdr_stream(mail, &hdr_size, &input) < 0)
+			return -1;
+		result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+		break;
+	case FETCH_HEADER_FIELDS:
+		/* try to lookup the headers from cache */
+		i_assert(msgpart->header_ctx != NULL);
+		if (mail_get_header_stream(mail, msgpart->header_ctx,
+					   &input) < 0)
+			return -1;
+		result_r->size_field = 0;
+		break;
+	case FETCH_BODY:
+		/* fetch the message's body */
+		if (mail_get_stream(mail, &hdr_size, &body_size, &input) < 0)
+			return -1;
+		result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+		break;
+	}
+	i_stream_seek(input, physical_pos);
 
-				if (subsection[6] == '\0') {
-					/* full */
-					hdr_size = part->header_size;
-					i_stream_ref(input);
-				} else if (strncmp(subsection, "HEADER.FIELDS ", 14) == 0) {
-					input = imap_msgpart_get_partial_header(
-							input, FALSE, section+14,
-							&hdr_size, error_r);
-				} else if (strncmp(subsection, "HEADER.FIELDS.NOT ", 18) == 0) {
-					input = imap_msgpart_get_partial_header(
-							input, TRUE, section+18,
-							&hdr_size, error_r);
-				} else {
-					input = NULL;
-				}
-
-				if (input != NULL) {
-					imap_msgpart_get_partial(&input, hdr_size,
-						partial_offset, partial_size,
-						stream_r, size_r);
-					return 1;
-				}
-			}
-		}
+	if (msgpart->headers != NULL) {
+		/* return specific headers */
+		if (imap_msgpart_get_partial_header(mail, input, msgpart,
+						    &hdr_size, result_r) < 0)
+			return -1;
+		imap_msgpart_get_partial(mail, msgpart, &hdr_size, result_r);
+		return 0;
 	}
 
-	*error_r = "Invalid section";
+	switch (msgpart->fetch_type) {
+	case FETCH_FULL:
+		part_size.physical_size += body_size.physical_size;
+		part_size.virtual_size += body_size.virtual_size;
+		/* fall through */
+	case FETCH_MIME:
+	case FETCH_HEADER:
+		part_size.physical_size += hdr_size.physical_size;
+		part_size.virtual_size += hdr_size.virtual_size;
+		break;
+	case FETCH_HEADER_FIELDS:
+	case FETCH_HEADER_FIELDS_NOT:
+		i_unreached();
+	case FETCH_BODY:
+		i_stream_skip(input, hdr_size.physical_size);
+		part_size.physical_size += body_size.physical_size;
+		part_size.virtual_size += body_size.virtual_size;
+		break;
+	}
+
+	result_r->input = input;
+	i_stream_ref(input);
+	imap_msgpart_get_partial(mail, msgpart, &part_size, result_r);
 	return 0;
 }
--- a/src/lib-imap-storage/imap-msgpart.h	Thu Jun 21 21:47:06 2012 +0300
+++ b/src/lib-imap-storage/imap-msgpart.h	Thu Jun 21 21:50:35 2012 +0300
@@ -1,25 +1,35 @@
 #ifndef IMAP_MSGPART_H
 #define IMAP_MSGPART_H
 
-/* Find message_part for section (eg. 1.3.4). Returns -1 if storage error,
-   0 otherwise. part_r is set to NULL if section doesn't exist. */
-int imap_msgpart_find(struct mail *mail, const char *section,
-		      const struct message_part **part_r,
-		      const char **subsection_r);
+struct imap_msgpart;
+
+struct imap_msgpart_open_result {
+	struct istream *input;
+	uoff_t size;
+	enum mail_fetch_field size_field;
+};
 
-/* Open message part refenced by IMAP section as istream. Returns 1 if
-   successful, 0 if section is invalid, -1 if storage error. Returned stream_r
-   stream may be NULL when there is no data to return. */
-int imap_msgpart_open(struct mail *mail, const char *section,
-		      uoff_t partial_offset, uoff_t partial_size,
-		      struct istream **stream_r,
-		      uoff_t *size_r, const char **error_r);
+struct imap_msgpart *imap_msgpart_full(void);
+struct imap_msgpart *imap_msgpart_header(void);
+struct imap_msgpart *imap_msgpart_body(void);
+/* Parse section into imap_msgpart. Returns 0 and msgpart_r on success,
+   -1 if the section isn't valid. The same imap_msgpart can be used to open
+   multiple messages. */
+int imap_msgpart_parse(struct mailbox *box, const char *section,
+		       struct imap_msgpart **msgpart_r);
+void imap_msgpart_free(struct imap_msgpart **msgpart);
 
-static inline int
-imap_msgpart_verify(struct mail *mail, const char *section,
-		    const char **error_r)
-{
-	return imap_msgpart_open(mail, section, 0, 0, NULL, NULL, error_r);
-}
+/* Set the fetch to be partial. For unlimited size use (uoff_t)-1. */
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+			      uoff_t offset, uoff_t size);
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart);
+/* Return wanted_fields mask. */
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart);
+
+/* Open message part refenced by IMAP section as istream. Returns 0 if
+   successful, -1 if storage error. Returned istream is initially referenced,
+   so i_stream_unref() must be called for it. */
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+		      struct imap_msgpart_open_result *result_r);
 
 #endif
--- a/src/lib-storage/mail-storage-private.h	Thu Jun 21 21:47:06 2012 +0300
+++ b/src/lib-storage/mail-storage-private.h	Thu Jun 21 21:50:35 2012 +0300
@@ -203,6 +203,12 @@
 	struct mail_storage_module_register *reg;
 };
 
+struct mail_msgpart_partial_cache {
+	uint32_t uid;
+	uoff_t physical_start;
+	uoff_t physical_pos, virtual_pos;
+};
+
 struct mailbox {
 	const char *name;
 	/* mailbox's virtual name (from mail_namespace_get_vname()) */
@@ -244,6 +250,7 @@
 	enum mailbox_flags flags;
 	unsigned int transaction_count;
 	enum mailbox_feature enabled_features;
+	struct mail_msgpart_partial_cache partial_cache;
 
 	struct mail_index_view *tmp_sync_view;