changeset 21627:6ced094b1759

lib-imap: imap-bodystructure: Changed struct message_part_body to contain fully decoded data. Contained unparsed IMAP string data before.
author Stephan Bosch <stephan.bosch@dovecot.fi>
date Sun, 23 Oct 2016 16:50:25 +0200
parents 591338b088aa
children a22181aad13a
files src/lib-imap/imap-bodystructure.c src/lib-imap/imap-bodystructure.h src/lib-imap/imap-envelope.c src/lib-storage/index/index-mail.c
diffstat 4 files changed, 312 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-imap/imap-bodystructure.c	Sun Oct 23 16:49:37 2016 +0200
+++ b/src/lib-imap/imap-bodystructure.c	Sun Oct 23 16:50:25 2016 +0200
@@ -12,20 +12,36 @@
 #include "imap-envelope.h"
 #include "imap-bodystructure.h"
 
-#define DEFAULT_CHARSET \
-	"\"charset\" \"us-ascii\""
+#define DEFAULT_CHARSET "us-ascii"
 
 #define EMPTY_BODYSTRUCTURE \
-        "(\"text\" \"plain\" ("DEFAULT_CHARSET") NIL NIL \"7bit\" 0 0)"
+        "(\"text\" \"plain\" (\"charset\" \""DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)"
 
-#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr))
+static void
+parse_mime_parameters(struct rfc822_parser_context *parser,
+	pool_t pool, const struct message_part_param **params_r,
+	unsigned int *params_count_r)
+{
+	const char *const *results;
+	struct message_part_param *params;
+	unsigned int params_count, i;
+
+	rfc2231_parse(parser, &results);
 
-static char *imap_get_string(pool_t pool, const char *value)
-{
-	string_t *str = t_str_new(64);
+	params_count = str_array_length(results);
+	i_assert((params_count % 2) == 0);
+	params_count /= 2;
 
-	imap_append_string(str, value);
-	return p_strdup(pool, str_c(str));
+	if (params_count > 0) {
+		params = p_new(pool, struct message_part_param, params_count);
+		for (i = 0; i < params_count; i++) {
+			params[i].name = p_strdup(pool, results[i*2+0]);
+			params[i].value = p_strdup(pool, results[i*2+1]);
+		}
+		*params_r = params;
+	}
+
+	*params_count_r = params_count;
 }
 
 static void
@@ -33,10 +49,9 @@
 	pool_t pool, struct message_header_line *hdr)
 {
 	struct rfc822_parser_context parser;
-	const char *value, *const *results;
 	string_t *str;
+	const char *value;
 	unsigned int i;
-	bool charset_found = FALSE;
 	int ret;
 
 	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
@@ -49,13 +64,12 @@
 	value = str_c(str);
 	for (i = 0; value[i] != '\0'; i++) {
 		if (value[i] == '/') {
-			data->content_subtype =
-				imap_get_string(pool, value + i+1);
+			data->content_subtype = p_strdup(pool, value + i+1);
 			break;
 		}
 	}
 	str_truncate(str, i);
-	data->content_type = imap_get_string(pool, str_c(str));
+	data->content_type = p_strdup(pool, str_c(str));
 
 	if (ret < 0) {
 		/* Content-Type is broken, but we wanted to get it as well as
@@ -67,29 +81,9 @@
 		return;
 	}
 
-	/* parse parameters and save them */
-	str_truncate(str, 0);
-	rfc2231_parse(&parser, &results);
-	for (; *results != NULL; results += 2) {
-		if (strcasecmp(results[0], "charset") == 0)
-			charset_found = TRUE;
-
-		str_append_c(str, ' ');
-		imap_append_string(str, results[0]);
-		str_append_c(str, ' ');
-		imap_append_string(str, results[1]);
-	}
-
-	if (!charset_found &&
-	    strcasecmp(data->content_type, "\"text\"") == 0) {
-		/* set a default charset */
-		str_append_c(str, ' ');
-		str_append(str, DEFAULT_CHARSET);
-	}
-	if (str_len(str) > 0) {
-		data->content_type_params =
-			p_strdup(pool, str_c(str) + 1);
-	}
+	parse_mime_parameters(&parser, pool,
+		&data->content_type_params,
+		&data->content_type_params_count);
 }
 
 static void
@@ -106,7 +100,7 @@
 	if (rfc822_parse_mime_token(&parser, str) >= 0 &&
 	    rfc822_skip_lwsp(&parser) == 0 && str_len(str) > 0) {
 		data->content_transfer_encoding =
-			imap_get_string(pool, str_c(str));
+			p_strdup(pool, str_c(str));
 	}
 }
 
@@ -115,7 +109,6 @@
 	pool_t pool, struct message_header_line *hdr)
 {
 	struct rfc822_parser_context parser;
-	const char *const *results;
 	string_t *str;
 
 	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
@@ -124,21 +117,11 @@
 	str = t_str_new(256);
 	if (rfc822_parse_mime_token(&parser, str) < 0)
 		return;
-	data->content_disposition = imap_get_string(pool, str_c(str));
+	data->content_disposition = p_strdup(pool, str_c(str));
 
-	/* parse parameters and save them */
-	str_truncate(str, 0);
-	rfc2231_parse(&parser, &results);
-	for (; *results != NULL; results += 2) {
-		str_append_c(str, ' ');
-		imap_append_string(str, results[0]);
-		str_append_c(str, ' ');
-		imap_append_string(str, results[1]);
-	}
-	if (str_len(str) > 0) {
-		data->content_disposition_params =
-			p_strdup(pool, str_c(str) + 1);
-	}
+	parse_mime_parameters(&parser, pool,
+		&data->content_disposition_params,
+		&data->content_disposition_params_count);
 }
 
 static void
@@ -146,6 +129,7 @@
 	pool_t pool, const unsigned char *value, size_t value_len)
 {
 	struct rfc822_parser_context parser;
+	ARRAY_TYPE(const_string) langs;
 	string_t *str;
 
 	/* Language-Header = "Content-Language" ":" 1#Language-tag
@@ -155,12 +139,15 @@
 
 	rfc822_parser_init(&parser, value, value_len, NULL);
 
+	t_array_init(&langs, 16);
 	str = t_str_new(128);
-	str_append_c(str, '"');
 
 	rfc822_skip_lwsp(&parser);
 	while (rfc822_parse_atom(&parser, str) >= 0) {
-		str_append(str, "\" \"");
+		const char *lang = p_strdup(pool, str_c(str));
+
+		array_append(&langs, &lang, 1);
+		str_truncate(str, 0);
 
 		if (parser.data == parser.end || *parser.data != ',')
 			break;
@@ -168,14 +155,16 @@
 		rfc822_skip_lwsp(&parser);
 	}
 
-	if (str_len(str) > 1) {
-		str_truncate(str, str_len(str) - 2);
-		data->content_language = p_strdup(pool, str_c(str));
+	if (array_count(&langs) > 0) {
+		array_append_zero(&langs);
+		data->content_language =
+			p_strarray_dup(pool, array_idx(&langs, 0));
 	}
 }
 
-static void parse_content_header(struct message_part_data *data,
-				 pool_t pool, struct message_header_line *hdr)
+static void
+parse_content_header(struct message_part_data *data,
+	pool_t pool, struct message_header_line *hdr)
 {
 	const char *name = hdr->name + strlen("Content-");
 	const char *value;
@@ -191,13 +180,13 @@
 	case 'i':
 	case 'I':
 		if (strcasecmp(name, "ID") == 0 && data->content_id == NULL)
-			data->content_id = imap_get_string(pool, value);
+			data->content_id = p_strdup(pool, value);
 		break;
 
 	case 'm':
 	case 'M':
 		if (strcasecmp(name, "MD5") == 0 && data->content_md5 == NULL)
-			data->content_md5 = imap_get_string(pool, value);
+			data->content_md5 = p_strdup(pool, value);
 		break;
 
 	case 't':
@@ -213,11 +202,11 @@
 	case 'L':
 		if (strcasecmp(name, "Language") == 0 &&
 		    data->content_language == NULL) {
-			parse_content_language(data, pool, hdr->full_value,
-					       hdr->full_value_len);
+			parse_content_language(data, pool,
+				hdr->full_value, hdr->full_value_len);
 		} else if (strcasecmp(name, "Location") == 0 &&
 			   data->content_location == NULL) {
-			data->content_location = imap_get_string(pool, value);
+			data->content_location = p_strdup(pool, value);
 		}
 		break;
 
@@ -225,7 +214,7 @@
 	case 'D':
 		if (strcasecmp(name, "Description") == 0 &&
 		    data->content_description == NULL)
-			data->content_description = imap_get_string(pool, value);
+			data->content_description = p_strdup(pool, value);
 		else if (strcasecmp(name, "Disposition") == 0 &&
 			 data->content_disposition_params == NULL)
 			parse_content_disposition(data, pool, hdr);
@@ -286,7 +275,40 @@
 }
 
 static void
-imap_bodystructure_write_siblings(const struct message_part *part,
+params_write(const struct message_part_param *params,
+	unsigned int params_count, string_t *str,
+	bool default_charset)
+{
+	unsigned int i;
+	bool seen_charset;
+
+	if (!default_charset && params_count == 0) {
+		str_append(str, "NIL");
+		return;
+	}
+	str_append_c(str, '(');
+
+	seen_charset = FALSE;
+	for (i = 0; i < params_count; i++) {
+		if (i > 0)
+			str_append_c(str, ' ');
+		if (default_charset &&
+			strcasecmp(params[i].name, "charset") == 0)
+			seen_charset = TRUE;
+		imap_append_string(str, params[i].name);
+		str_append_c(str, ' ');
+		imap_append_string(str, params[i].value);
+	}
+	if (default_charset && !seen_charset) {
+		if (i > 0)
+			str_append_c(str, ' ');
+		str_append(str, "\"charset\" \""DEFAULT_CHARSET"\"");
+	}
+	str_append_c(str, ')');
+}
+
+static void
+part_write_bodystructure_siblings(const struct message_part *part,
 				  string_t *dest, bool extended)
 {
 	for (; part != NULL; part = part->next) {
@@ -297,7 +319,7 @@
 }
 
 static void
-part_write_bodystructure_data_common(struct message_part_data *data,
+part_write_bodystructure_common(const struct message_part_data *data,
 				     string_t *str)
 {
 	str_append_c(str, ' ');
@@ -305,16 +327,12 @@
 		str_append(str, "NIL");
 	else {
 		str_append_c(str, '(');
-		str_append(str, data->content_disposition);
-		str_append_c(str, ' ');
+		imap_append_string(str, data->content_disposition);
 
-		if (data->content_disposition_params == NULL)
-			str_append(str, "NIL");
-		else {
-			str_append_c(str, '(');
-			str_append(str, data->content_disposition_params);
-			str_append_c(str, ')');
-		}
+		str_append_c(str, ' ');
+		params_write(data->content_disposition_params,
+			data->content_disposition_params_count, str, FALSE);
+
 		str_append_c(str, ')');
 	}
 
@@ -322,22 +340,33 @@
 	if (data->content_language == NULL)
 		str_append(str, "NIL");
 	else {
+		const char *const *lang = data->content_language;
+
+		i_assert(*lang != NULL);
 		str_append_c(str, '(');
-		str_append(str, data->content_language);
+		imap_append_string(str, *lang);
+		lang++;
+		while (*lang != NULL) {
+			str_append_c(str, ' ');
+			imap_append_string(str, *lang);
+			lang++;
+		}
 		str_append_c(str, ')');
 	}
 
 	str_append_c(str, ' ');
-	str_append(str, NVL(data->content_location, "NIL"));
+	imap_append_nstring(str, data->content_location);
 }
 
 static void part_write_body_multipart(const struct message_part *part,
 				      string_t *str, bool extended)
 {
-	struct message_part_data *data = part->data;
+	const struct message_part_data *data = part->data;
+
+	i_assert(part->data != NULL);
 
 	if (part->children != NULL)
-		imap_bodystructure_write_siblings(part->children, str, extended);
+		part_write_bodystructure_siblings(part->children, str, extended);
 	else {
 		/* no parts in multipart message,
 		   that's not allowed. write a single
@@ -347,7 +376,7 @@
 
 	str_append_c(str, ' ');
 	if (data->content_subtype != NULL)
-		str_append(str, data->content_subtype);
+		imap_append_string(str, data->content_subtype);
 	else
 		str_append(str, "\"x-unknown\"");
 
@@ -355,84 +384,79 @@
 		return;
 
 	/* BODYSTRUCTURE data */
+
 	str_append_c(str, ' ');
-	if (data->content_type_params == NULL)
-		str_append(str, "NIL");
-	else {
-		str_append_c(str, '(');
-		str_append(str, data->content_type_params);
-		str_append_c(str, ')');
-	}
+	params_write(data->content_type_params,
+		data->content_type_params_count, str, FALSE);
 
-	part_write_bodystructure_data_common(data, str);
+	part_write_bodystructure_common(data, str);
 }
 
 static void part_write_body(const struct message_part *part,
 			    string_t *str, bool extended)
 {
-	struct message_part_data *data = part->data;
+	const struct message_part_data *data = part->data;
 	bool text;
 
-	if (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) {
+	i_assert(part->data != NULL);
+
+	if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
 		str_append(str, "\"message\" \"rfc822\"");
 		text = FALSE;
 	} else {
 		/* "content type" "subtype" */
-		text = data->content_type == NULL ||
-			strcasecmp(data->content_type, "\"text\"") == 0;
-		str_append(str, NVL(data->content_type, "\"text\""));
+		if (data->content_type == NULL) {
+			text = TRUE;
+			str_append(str, "\"text\"");
+		} else {
+			text = (strcasecmp(data->content_type, "text") == 0);
+			imap_append_string(str, data->content_type);
+		}
 		str_append_c(str, ' ');
 
 		if (data->content_subtype != NULL)
-			str_append(str, data->content_subtype);
+			imap_append_string(str, data->content_subtype);
 		else {
 			if (text)
 				str_append(str, "\"plain\"");
 			else
 				str_append(str, "\"unknown\"");
-
 		}
 	}
 
 	/* ("content type param key" "value" ...) */
 	str_append_c(str, ' ');
-	if (data->content_type_params == NULL) {
-		if (!text)
-			str_append(str, "NIL");
-		else
-			str_append(str, "("DEFAULT_CHARSET")");
-	} else {
-		str_append_c(str, '(');
-		str_append(str, data->content_type_params);
-		str_append_c(str, ')');
-	}
+	params_write(data->content_type_params,
+		data->content_type_params_count, str, text);
 
-	str_printfa(str, " %s %s %s %"PRIuUOFF_T,
-		    NVL(data->content_id, "NIL"),
-		    NVL(data->content_description, "NIL"),
-		    NVL(data->content_transfer_encoding, "\"7bit\""),
-		    part->body_size.virtual_size);
+	str_append_c(str, ' ');
+	imap_append_nstring(str, data->content_id);
+	str_append_c(str, ' ');
+	imap_append_nstring(str, data->content_description);
+	str_append_c(str, ' ');
+	if (data->content_transfer_encoding != NULL)
+		imap_append_string(str, data->content_transfer_encoding);
+	else
+		str_append(str, "\"7bit\"");
+	str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size);
 
 	if (text) {
 		/* text/.. contains line count */
 		str_printfa(str, " %u", part->body_size.lines);
 	} else if (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) {
 		/* message/rfc822 contains envelope + body + line count */
-		struct message_part_data *child_data;
+		const struct message_part_data *child_data;
 
 		i_assert(part->children != NULL);
 		i_assert(part->children->next == NULL);
 
-                child_data = part->children->data;
+		child_data = part->children->data;
 
 		str_append(str, " (");
-		if (child_data->envelope_str != NULL)
-			str_append(str, child_data->envelope_str);
-		else
-			imap_envelope_write_part_data(child_data->envelope, str);
+		imap_envelope_write_part_data(child_data->envelope, str);
 		str_append(str, ") ");
 
-		imap_bodystructure_write_siblings(part->children, str, extended);
+		part_write_bodystructure_siblings(part->children, str, extended);
 		str_printfa(str, " %u", part->body_size.lines);
 	}
 
@@ -444,8 +468,8 @@
 	/* "md5" ("content disposition" ("disposition" "params"))
 	   ("body" "language" "params") "location" */
 	str_append_c(str, ' ');
-	str_append(str, NVL(data->content_md5, "NIL"));
-	part_write_bodystructure_data_common(data, str);
+	imap_append_nstring(str, data->content_md5);
+	part_write_bodystructure_common(data, str);
 }
 
 bool imap_bodystructure_is_plain_7bit(const struct message_part *part)
@@ -463,12 +487,13 @@
 
 	/* must be text/plain */
 	if (data->content_subtype != NULL &&
-	    strcasecmp(data->content_subtype, "\"plain\"") != 0)
+	    strcasecmp(data->content_subtype, "plain") != 0)
 		return FALSE;
 
 	/* only allowed parameter is charset=us-ascii, which is also default */
-	if (data->content_type_params != NULL &&
-	    strcasecmp(data->content_type_params, DEFAULT_CHARSET) != 0)
+	if (data->content_type_params_count > 0 &&
+	    (strcasecmp(data->content_type_params[0].name, "charset") != 0 ||
+	     strcasecmp(data->content_type_params[0].value, DEFAULT_CHARSET) != 0))
 		return FALSE;
 
 	if (data->content_id != NULL ||
@@ -476,7 +501,7 @@
 		return FALSE;
 
 	if (data->content_transfer_encoding != NULL &&
-	    strcasecmp(data->content_transfer_encoding, "\"7bit\"") != 0)
+	    strcasecmp(data->content_transfer_encoding, "7bit") != 0)
 		return FALSE;
 
 	/* BODYSTRUCTURE checks: */
@@ -531,22 +556,6 @@
 	return TRUE;
 }
 
-static bool
-get_nstring(const struct imap_arg *arg, pool_t pool, string_t *tmpstr,
-	    const char **value_r)
-{
-	if (arg->type == IMAP_ARG_NIL) {
-		*value_r = NULL;
-		return TRUE;
-	}
-
-	str_truncate(tmpstr, 0);
-	if (!str_append_nstring(tmpstr, arg))
-		return FALSE;
-	*value_r = p_strdup(pool, str_c(tmpstr));
-	return TRUE;
-}
-
 static void
 imap_write_envelope_list(const struct imap_arg *args, string_t *str,
 	bool toplevel)
@@ -582,74 +591,77 @@
 	imap_write_envelope_list(args, str, TRUE);
 }
 
-static int imap_write_nstring_list(const struct imap_arg *args, string_t *str)
+static int
+imap_bodystructure_strlist_parse(const struct imap_arg *arg,
+	pool_t pool, const char *const **list_r)
 {
-	str_truncate(str, 0);
-	while (!IMAP_ARG_IS_EOL(args)) {
-		if (!str_append_nstring(str, &args[0]))
-			return -1;
-		args++;
-		if (IMAP_ARG_IS_EOL(args))
-			break;
-		str_append_c(str, ' ');
-	}
-	return 0;
-}
-
-static int imap_write_params(const struct imap_arg *arg, pool_t pool,
-			     string_t *tmpstr, unsigned int divisible,
-			     const char **value_r)
-{
+	const char *item, **list;
 	const struct imap_arg *list_args;
-	unsigned int list_count;
+	unsigned int list_count, i;
 
 	if (arg->type == IMAP_ARG_NIL) {
-		*value_r = NULL;
+		*list_r = NULL;
 		return 0;
 	}
-	if (arg->type == IMAP_ARG_STRING) {
-		if (divisible > 1)
-			return -1;
-		str_truncate(tmpstr, 0);
-		str_append_nstring(tmpstr, arg);
+	if (imap_arg_get_nstring(arg, &item)) {
+		list = p_new(pool, const char *, 2);
+		list[0] = p_strdup(pool, item);
 	} else {
 		if (!imap_arg_get_list_full(arg, &list_args, &list_count))
 			return -1;
-		if ((list_count % divisible) != 0)
-			return -1;
-		if (imap_write_nstring_list(list_args, tmpstr) < 0)
-			return -1;
+
+		list = p_new(pool, const char *, list_count+1);
+		for (i = 0; i < list_count; i++) {
+			if (!imap_arg_get_nstring(&list_args[i], &item))
+				return -1;
+			list[i] = p_strdup(pool, item);
+		}
 	}
-	*value_r = p_strdup(pool, str_c(tmpstr));
+	*list_r = list;
 	return 0;
 }
 
 static int
-imap_bodystructure_parse_lines(const struct imap_arg *arg,
-			       const struct message_part *part,
-			       const char **error_r)
+imap_bodystructure_params_parse(const struct imap_arg *arg,
+	pool_t pool, const struct message_part_param **params_r,
+	unsigned int *count_r)
 {
-	const char *value;
-	unsigned int lines;
-	
-	if (!imap_arg_get_atom(arg, &value) ||
-	    str_to_uint(value, &lines) < 0) {
-		*error_r = "Invalid lines field";
+	struct message_part_param *params;
+	const struct imap_arg *list_args;
+	unsigned int list_count, params_count, i;
+
+	if (arg->type == IMAP_ARG_NIL) {
+		*params_r = NULL;
+		return 0;
+	}
+	if (!imap_arg_get_list_full(arg, &list_args, &list_count))
+		return -1;
+	if ((list_count % 2) != 0)
 		return -1;
+
+	params_count = list_count/2;
+	params = p_new(pool, struct message_part_param, params_count+1);
+	for (i = 0; i < params_count; i++) {
+		const char *name, *value;
+
+		if (!imap_arg_get_nstring(&list_args[i*2+0], &name))
+			return -1;
+		if (!imap_arg_get_nstring(&list_args[i*2+1], &value))
+			return -1;
+		params[i].name = p_strdup(pool, name);
+		params[i].value = p_strdup(pool, value);
 	}
-	if (lines != part->body_size.lines) {
-		*error_r = "message_part lines doesn't match lines in BODYSTRUCTURE";
-		return -1;
-	}
+	*params_r = params;
+	*count_r = params_count;
 	return 0;
 }
 
 static int
-imap_bodystructure_parse_args_common(struct message_part_data *data,
-				     pool_t pool, string_t *tmpstr,
-				     const struct imap_arg *args,
+imap_bodystructure_parse_args_common(struct message_part *part,
+				     pool_t pool, const struct imap_arg *args,
 				     const char **error_r)
 {
+	struct message_part_data *data = part->data;
 	const struct imap_arg *list_args;
 
 	if (args->type == IMAP_ARG_EOL)
@@ -660,13 +672,15 @@
 		*error_r = "Invalid content-disposition list";
 		return -1;
 	} else {
-		if (!get_nstring(list_args++, pool, tmpstr,
-				 &data->content_disposition)) {
+		if (!imap_arg_get_nstring
+			(list_args++, &data->content_disposition)) {
 			*error_r = "Invalid content-disposition";
 			return -1;
 		}
-		if (imap_write_params(list_args, pool, tmpstr, 2,
-				      &data->content_disposition_params) < 0) {
+		data->content_disposition = p_strdup(pool, data->content_disposition);
+		if (imap_bodystructure_params_parse(list_args, pool,
+			&data->content_disposition_params,
+			&data->content_disposition_params_count) < 0) {
 			*error_r = "Invalid content-disposition params";
 			return -1;
 		}
@@ -674,34 +688,36 @@
 	}
 	if (args->type == IMAP_ARG_EOL)
 		return 0;
-	if (imap_write_params(args++, pool, tmpstr, 1,
-			      &data->content_language) < 0) {
+	if (imap_bodystructure_strlist_parse
+		(args++, pool, &data->content_language) < 0) {
 		*error_r = "Invalid content-language";
 		return -1;
 	}
 	if (args->type == IMAP_ARG_EOL)
 		return 0;
-	if (!get_nstring(args++, pool, tmpstr, &data->content_location)) {
+	if (!imap_arg_get_nstring
+		(args++, &data->content_location)) {
 		*error_r = "Invalid content-location";
 		return -1;
 	}
+	data->content_location = p_strdup(pool, data->content_location);
 	return 0;
 }
 
 static int
 imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool,
-			      struct message_part *part, string_t *tmpstr,
+			      struct message_part *part,
 			      const char **error_r)
 {
 	struct message_part_data *data;
 	struct message_part *child_part;
 	const struct imap_arg *list_args;
-	const char *value, *content_type, *subtype;
+	const char *value, *content_type, *subtype, *error;
+	bool multipart, text, message_rfc822, has_lines;
+	unsigned int lines;
 	uoff_t vsize;
-	bool multipart, text, message_rfc822;
 
 	i_assert(part->data == NULL);
-
 	part->data = data = p_new(pool, struct message_part_data, 1);
 
 	multipart = FALSE;
@@ -715,8 +731,7 @@
 
 		list_args = imap_arg_as_list(args);
 		if (imap_bodystructure_parse_args(list_args, pool,
-						  child_part, tmpstr,
-						  error_r) < 0)
+						  child_part, error_r) < 0)
 			return -1;
 		child_part = child_part->next;
 
@@ -729,21 +744,22 @@
 			*error_r = "message_part hierarchy doesn't match BODYSTRUCTURE";
 			return -1;
 		}
-
-		data->content_type = "\"multipart\"";
-		if (!get_nstring(args++, pool, tmpstr, &data->content_subtype)) {
+		data->content_type = "multipart";
+		if (!imap_arg_get_nstring(args++, &data->content_subtype)) {
 			*error_r = "Invalid multipart content-type";
 			return -1;
 		}
+		data->content_subtype = p_strdup(pool, data->content_subtype);
 		if (args->type == IMAP_ARG_EOL)
 			return 0;
-		if (imap_write_params(args++, pool, tmpstr, 2,
-				      &data->content_type_params) < 0) {
+		if (imap_bodystructure_params_parse(args++, pool,
+			&data->content_type_params,
+			&data->content_type_params_count) < 0) {
 			*error_r = "Invalid content params";
 			return -1;
 		}
-		return imap_bodystructure_parse_args_common(data, pool, tmpstr,
-							    args, error_r);
+		return imap_bodystructure_parse_args_common
+			(part, pool, args, error_r);
 	}
 	if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
 		*error_r = "message_part multipart flag doesn't match BODYSTRUCTURE";
@@ -756,15 +772,14 @@
 		*error_r = "Invalid content-type";
 		return -1;
 	}
-
-	if (!get_nstring(&args[0], pool, tmpstr, &data->content_type) ||
-	    !get_nstring(&args[1], pool, tmpstr, &data->content_subtype))
-		i_unreached();
+	data->content_type = p_strdup(pool, content_type);
+	data->content_subtype = p_strdup(pool, subtype);
 	args += 2;
 
 	text = strcasecmp(content_type, "text") == 0;
 	message_rfc822 = strcasecmp(content_type, "message") == 0 &&
 		strcasecmp(subtype, "rfc822") == 0;
+
 #if 0
 	/* Disabled for now. Earlier Dovecot versions handled broken
 	   Content-Type headers by writing them as "text" "plain" to
@@ -781,25 +796,30 @@
 	}
 
 	/* ("content type param key" "value" ...) | NIL */
-	if (imap_write_params(args++, pool, tmpstr, 2,
-			      &data->content_type_params) < 0) {
+	if (imap_bodystructure_params_parse(args++, pool,
+		&data->content_type_params,
+		&data->content_type_params_count) < 0) {
 		*error_r = "Invalid content params";
 		return -1;
 	}
 
 	/* "content id" "content description" "transfer encoding" size */
-	if (!get_nstring(args++, pool, tmpstr, &data->content_id)) {
+	if (!imap_arg_get_nstring(args++, &data->content_id)) {
 		*error_r = "Invalid content-id";
 		return -1;
 	}
-	if (!get_nstring(args++, pool, tmpstr, &data->content_description)) {
+	if (!imap_arg_get_nstring(args++, &data->content_description)) {
 		*error_r = "Invalid content-description";
 		return -1;
 	}
-	if (!get_nstring(args++, pool, tmpstr, &data->content_transfer_encoding)) {
+	if (!imap_arg_get_nstring(args++, &data->content_transfer_encoding)) {
 		*error_r = "Invalid content-transfer-encoding";
 		return -1;
 	}
+	data->content_id = p_strdup(pool, data->content_id);
+	data->content_description = p_strdup(pool, data->content_description);
+	data->content_transfer_encoding =
+		p_strdup(pool, data->content_transfer_encoding);
 	if (!imap_arg_get_atom(args++, &value) ||
 	    str_to_uoff(value, &vsize) < 0) {
 		*error_r = "Invalid size field";
@@ -813,13 +833,15 @@
 
 	if (text) {
 		/* text/xxx - text lines */
-		if (imap_bodystructure_parse_lines(args, part, error_r) < 0)
+		if (!imap_arg_get_atom(args++, &value) ||
+		    str_to_uint(value, &lines) < 0) {
+			*error_r = "Invalid lines field";
 			return -1;
-		args++;
+		}
 		i_assert(part->children == NULL);
+		has_lines = TRUE;
 	} else if (message_rfc822) {
 		/* message/rfc822 - envelope + bodystructure + text lines */
-		struct message_part_data *child_data;
 
 		i_assert(part->children != NULL &&
 			 part->children->next == NULL);
@@ -828,45 +850,55 @@
 			*error_r = "Child bodystructure list expected";
 			return -1;
 		}
-		if (imap_bodystructure_parse_args(list_args, pool,
-						  part->children,
-						  tmpstr, error_r) < 0)
+		if (imap_bodystructure_parse_args
+			(list_args, pool, part->children, error_r) < 0)
 			return -1;
 
-		/* save envelope to the child's context data */
 		if (!imap_arg_get_list(&args[0], &list_args)) {
 			*error_r = "Envelope list expected";
 			return -1;
 		}
-		str_truncate(tmpstr, 0);
-		imap_write_envelope(list_args, tmpstr);
-		child_data = part->children->data;
-		child_data->envelope_str = p_strdup(pool, str_c(tmpstr));
-
+		if (!imap_envelope_parse_args(list_args, pool,
+			&part->children->data->envelope, &error)) {
+			*error_r = t_strdup_printf
+				("Invalid envelope list: %s", error);
+			return -1;
+		}
 		args += 2;
-		if (imap_bodystructure_parse_lines(args, part, error_r) < 0)
+		if (!imap_arg_get_atom(args++, &value) ||
+		    str_to_uint(value, &lines) < 0) {
+			*error_r = "Invalid lines field";
 			return -1;
-		args++;
+		}
+		has_lines = TRUE;
 	} else {
 		i_assert(part->children == NULL);
+		has_lines = FALSE;
 	}
-
+	if (has_lines && lines != part->body_size.lines) {
+		*error_r = "message_part lines "
+			"doesn't match lines in BODYSTRUCTURE";
+		return -1;
+	}
 	if (args->type == IMAP_ARG_EOL)
 		return 0;
-	if (!get_nstring(args++, pool, tmpstr, &data->content_md5)) {
+	if (!imap_arg_get_nstring(args++, &data->content_md5)) {
 		*error_r = "Invalid content-md5";
 		return -1;
 	}
-	return imap_bodystructure_parse_args_common(data, pool, tmpstr,
-						    args, error_r);
+	data->content_md5 = p_strdup(pool, data->content_md5);
+	return imap_bodystructure_parse_args_common
+		(part, pool, args, error_r);
 }
 
-int imap_bodystructure_parse(const char *bodystructure, pool_t pool,
-			     struct message_part *parts, const char **error_r)
+int imap_bodystructure_parse(const char *bodystructure,
+	pool_t pool, struct message_part *parts,
+	const char **error_r)
 {
 	struct istream *input;
 	struct imap_parser *parser;
 	const struct imap_arg *args;
+	char *error;
 	int ret;
 	bool fatal;
 
@@ -877,7 +909,7 @@
 	(void)i_stream_read(input);
 
 	parser = imap_parser_create(input, NULL, (size_t)-1);
-	ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE |
+	ret = imap_parser_finish_line(parser, 0,
 				      IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
 	if (ret < 0) {
 		*error_r = t_strdup_printf("IMAP parser failed: %s",
@@ -885,11 +917,19 @@
 	} else if (ret == 0) {
 		*error_r = "Empty bodystructure";
 		ret = -1;
-	} else T_BEGIN {
-		string_t *tmpstr = t_str_new(256);
-		ret = imap_bodystructure_parse_args(args, pool, parts,
-						    tmpstr, error_r);
-	} T_END;
+	} else {
+		T_BEGIN {
+			ret = imap_bodystructure_parse_args
+				(args, pool, parts, error_r);
+			if (ret < 0)
+				error = i_strdup(*error_r);
+		} T_END;
+
+		if (ret < 0) {
+			*error_r = t_strdup(error);
+			i_free(error);
+		}
+	}
 
 	imap_parser_unref(&parser);
 	i_stream_destroy(&input);
--- a/src/lib-imap/imap-bodystructure.h	Sun Oct 23 16:49:37 2016 +0200
+++ b/src/lib-imap/imap-bodystructure.h	Sun Oct 23 16:50:25 2016 +0200
@@ -1,23 +1,27 @@
 #ifndef IMAP_BODYSTRUCTURE_H
 #define IMAP_BODYSTRUCTURE_H
 
+struct message_part_param {
+	const char *name;
+	const char *value;
+};
+
 struct message_part_data {
-	/* NOTE: all the strings are stored via imap_quote(), so they contain
-	   "quoted-text" or {123}\r\nliterals */
 	const char *content_type, *content_subtype;
-	const char *content_type_params; /* "key" "value" "key2" "value2" .. */
+	const struct message_part_param *content_type_params;
+	unsigned int content_type_params_count;
+
 	const char *content_transfer_encoding;
 	const char *content_id;
 	const char *content_description;
 	const char *content_disposition;
-	const char *content_disposition_params; /* "key" "value" "key2" "value2" .. */
+	const struct message_part_param *content_disposition_params;
+	unsigned int content_disposition_params_count;
 	const char *content_md5;
-	const char *content_language; /* "lang1" "lang2" "lang3" .. */
+	const char *const *content_language;
 	const char *content_location;
 
-	/* either one of these is set, but not both: */
 	struct message_part_envelope_data *envelope;
-	const char *envelope_str;
 };
 
 struct message_part;
--- a/src/lib-imap/imap-envelope.c	Sun Oct 23 16:49:37 2016 +0200
+++ b/src/lib-imap/imap-envelope.c	Sun Oct 23 16:50:25 2016 +0200
@@ -144,18 +144,10 @@
 		*addr_p = message_address_parse(pool, hdr->full_value,
 						hdr->full_value_len,
 						UINT_MAX, TRUE);
-	} else if (str_p != NULL) T_BEGIN {
-		string_t *str = t_str_new(128);
-
-		if (str_p != &d->subject) {
-			imap_append_string(str,
-				t_strndup(hdr->full_value, hdr->full_value_len));
-		} else {
-			imap_append_string_for_humans(str,
-				hdr->full_value, hdr->full_value_len);
-		}
-		*str_p = p_strdup(pool, str_c(str));
-	} T_END;
+	} else if (str_p != NULL) {
+		*str_p = p_strndup(pool,
+			hdr->full_value, hdr->full_value_len);
+	}
 }
 
 static void imap_write_address(string_t *str, struct message_address *addr)
@@ -199,9 +191,15 @@
 		return;
 	}
 
-	str_append(str, NVL(data->date, "NIL"));
+	imap_append_nstring(str, data->date);
 	str_append_c(str, ' ');
-	str_append(str, NVL(data->subject, "NIL"));
+	if (data->subject == NULL)
+		str_append(str, "NIL");
+	else {
+		imap_append_string_for_humans(str,
+			(const unsigned char *)data->subject,
+			strlen(data->subject));
+	}
 
 	str_append_c(str, ' ');
 	imap_write_address(str, data->from);
@@ -217,9 +215,9 @@
 	imap_write_address(str, data->bcc);
 
 	str_append_c(str, ' ');
-	str_append(str, NVL(data->in_reply_to, "NIL"));
+	imap_append_nstring(str, data->in_reply_to);
 	str_append_c(str, ' ');
-	str_append(str, NVL(data->message_id, "NIL"));
+	imap_append_nstring(str, data->message_id);
 }
 
 static bool
--- a/src/lib-storage/index/index-mail.c	Sun Oct 23 16:49:37 2016 +0200
+++ b/src/lib-storage/index/index-mail.c	Sun Oct 23 16:50:25 2016 +0200
@@ -950,17 +950,17 @@
 	i_assert(body_data != NULL);
 
 	if (body_data->content_type == NULL ||
-	    strcasecmp(body_data->content_type, "\"text\"") == 0) {
+	    strcasecmp(body_data->content_type, "text") == 0) {
 		/* use any text/ part, even if we don't know what exactly
 		   it is. */
 		return parts;
 	}
-	if (strcasecmp(body_data->content_type, "\"multipart\"") != 0) {
+	if (strcasecmp(body_data->content_type, "multipart") != 0) {
 		/* for now we support only text Content-Types */
 		return NULL;
 	}
 
-	if (strcasecmp(body_data->content_subtype, "\"alternative\"") == 0) {
+	if (strcasecmp(body_data->content_subtype, "alternative") == 0) {
 		/* text/plain > text/html > text/ */
 		struct message_part *html_part = NULL, *text_part = NULL;
 
@@ -971,11 +971,11 @@
 			i_assert(sub_body_data != NULL);
 
 			if (sub_body_data->content_type == NULL ||
-			    strcasecmp(sub_body_data->content_type, "\"text\"") == 0) {
+			    strcasecmp(sub_body_data->content_type, "text") == 0) {
 				if (sub_body_data->content_subtype == NULL ||
-				    strcasecmp(sub_body_data->content_subtype, "\"plain\"") == 0)
+				    strcasecmp(sub_body_data->content_subtype, "plain") == 0)
 					return part;
-				if (strcasecmp(sub_body_data->content_subtype, "\"html\"") == 0)
+				if (strcasecmp(sub_body_data->content_subtype, "html") == 0)
 					html_part = part;
 				else
 					text_part = part;