changeset 21641:e9a0ccfe9b67

Partially implemented IMAP SEARCH=X-MIMEPART capability. This capability is currently Dovecot-specific.
author Stephan Bosch <stephan@dovecot.fi>
date Wed, 25 May 2016 01:57:08 +0200
parents 16358228dd24
children 720e5963f3ca
files src/lib-storage/Makefile.am src/lib-storage/index/Makefile.am src/lib-storage/index/imapc/imapc-search.c src/lib-storage/index/index-search-mime.c src/lib-storage/index/index-search-private.h src/lib-storage/index/index-search.c src/lib-storage/mail-search-args-cmdline.c src/lib-storage/mail-search-args-imap.c src/lib-storage/mail-search-mime-build.c src/lib-storage/mail-search-mime-build.h src/lib-storage/mail-search-mime-register.c src/lib-storage/mail-search-mime-register.h src/lib-storage/mail-search-mime.c src/lib-storage/mail-search-mime.h src/lib-storage/mail-search-register-imap.c src/lib-storage/mail-search.c src/lib-storage/mail-search.h src/lib-storage/mail-storage.c src/lib-storage/test-mail-search-args-imap.c
diffstat 19 files changed, 2215 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-storage/Makefile.am	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/Makefile.am	Wed May 25 01:57:08 2016 +0200
@@ -33,6 +33,9 @@
 	mail-search-args-imap.c \
 	mail-search-args-simplify.c \
 	mail-search-build.c \
+	mail-search-mime.c \
+	mail-search-mime-build.c \
+	mail-search-mime-register.c \
 	mail-search-parser.c \
 	mail-search-parser-imap.c \
 	mail-search-parser-cmdline.c \
@@ -67,6 +70,9 @@
 	mail-namespace.h \
 	mail-search.h \
 	mail-search-build.h \
+	mail-search-mime.h \
+	mail-search-mime-build.h \
+	mail-search-mime-register.h \
 	mail-search-register.h \
 	mail-thread.h \
 	mail-storage.h \
--- a/src/lib-storage/index/Makefile.am	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/index/Makefile.am	Wed May 25 01:57:08 2016 +0200
@@ -23,6 +23,7 @@
 	index-pop3-uidl.c \
 	index-rebuild.c \
 	index-search.c \
+	index-search-mime.c \
 	index-search-result.c \
 	index-sort.c \
 	index-sort-string.c \
--- a/src/lib-storage/index/imapc/imapc-search.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/index/imapc/imapc-search.c	Wed May 25 01:57:08 2016 +0200
@@ -135,6 +135,7 @@
 	case SEARCH_MAILBOX_GUID:
 	case SEARCH_MAILBOX_GLOB:
 	case SEARCH_REAL_UID:
+	case SEARCH_MIMEPART:
 		/* not supported for now */
 		break;
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-search-mime.c	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,563 @@
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "message-date.h"
+#include "message-address.h"
+#include "message-part-data.h"
+#include "imap-bodystructure.h"
+#include "mail-search.h"
+#include "mail-search-mime.h"
+#include "index-search-private.h"
+
+struct search_mimepart_stack {
+	unsigned int index;
+};
+
+struct search_mimepart_context {
+	pool_t pool;
+	struct index_search_context *index_ctx;
+
+	/* message parts parsed from BODYSTRUCTURE */
+	struct message_part *mime_parts, *mime_part;
+
+	unsigned int depth, index;
+	ARRAY(struct search_mimepart_stack) stack;
+};
+
+static void search_mime_arg(struct mail_search_mime_arg *arg,
+			      struct search_mimepart_context *mpctx);
+
+static int seach_arg_mime_parent_match(struct search_mimepart_context *mpctx,
+				   struct mail_search_mime_arg *args)
+{
+	struct message_part *part = mpctx->mime_part;
+	unsigned int prev_depth, prev_index;
+	struct search_mimepart_stack *level;
+	int ret;
+
+	if (args->value.subargs == NULL) {
+		/* PARENT EXISTS: matches if this part has a parent.
+		 */
+		return (part->parent != NULL ? 1 : 0);
+	}
+
+	/* PARENT <mpart-key>: matches if this part's parent matches the
+	   mpart-key (subargs).
+	 */
+
+	prev_depth = mpctx->depth;
+	prev_index = mpctx->index;
+
+	level = array_idx_modifiable
+		(&mpctx->stack, mpctx->depth-1);
+
+	mpctx->mime_part = part->parent;
+	mail_search_mime_args_reset(args->value.subargs, TRUE);
+
+	mpctx->index = level->index;
+	mpctx->depth = mpctx->depth-1;
+	ret = mail_search_mime_args_foreach
+		(args->value.subargs, search_mime_arg, mpctx);
+
+	mpctx->mime_part = part;
+	mpctx->index = prev_index;
+	mpctx->depth = prev_depth;
+	return ret;
+}
+
+static int seach_arg_mime_child_match(struct search_mimepart_context *mpctx,
+				   struct mail_search_mime_arg *args)
+{
+	struct message_part *part, *prev_part;
+	unsigned int prev_depth, prev_index, depth;
+	struct search_mimepart_stack *level;
+	int ret = 0;
+
+	part = mpctx->mime_part;
+	if (args->value.subargs == NULL) {
+		/* CHILD EXISTS: matches if this part has any children; i.e., it is
+		   multipart.
+		 */
+		return (part->children != NULL ? 1 : 0);
+	}
+
+	/* CHILD <mpart-key>: matches if this part has any child that mathes
+	   the mpart-key (subargs).
+	 */
+
+	prev_part = part;
+	prev_depth = mpctx->depth;
+	prev_index = mpctx->index;
+
+	depth = mpctx->depth;
+	T_BEGIN {
+		ARRAY(struct search_mimepart_stack) prev_stack;
+
+		/* preserve current stack for any nested CHILD PARENT nastyness */
+		t_array_init(&prev_stack, 16);
+		array_copy(&prev_stack.arr, 0, &mpctx->stack.arr, 0,
+			array_count(&mpctx->stack));
+
+		depth++;
+		if (depth < array_count(&mpctx->stack))
+			level = array_idx_modifiable(&mpctx->stack, depth);
+		else {
+			i_assert(depth == array_count(&mpctx->stack));
+			level = array_append_space(&mpctx->stack);
+		}
+		level->index = 1;
+
+		part = part->children;
+		while (part != NULL) {
+			mpctx->mime_part = part;
+			mail_search_mime_args_reset(args->value.subargs, TRUE);
+
+			mpctx->depth = depth - prev_depth;
+			mpctx->index = level->index;
+			if ((ret=mail_search_mime_args_foreach
+				(args->value.subargs, search_mime_arg, mpctx)) != 0)
+				break;
+			if (part->children != NULL) {
+				depth++;
+				if (depth < array_count(&mpctx->stack))
+					level = array_idx_modifiable(&mpctx->stack, depth);
+				else {
+					i_assert(depth == array_count(&mpctx->stack));
+					level = array_append_space(&mpctx->stack);
+				}
+				level->index = 1;
+				part = part->children;
+			} else {
+				while (part->next == NULL) {
+					if (part->parent == NULL || part->parent == prev_part)
+						break;
+					depth--;
+					level = array_idx_modifiable(&mpctx->stack, depth);
+					part = part->parent;
+				}
+				level->index++;
+				part = part->next;
+			}
+		}
+
+		array_clear(&mpctx->stack);
+		array_copy(&mpctx->stack.arr, 0, &prev_stack.arr, 0,
+			array_count(&prev_stack));
+	} T_END;
+
+	mpctx->mime_part = prev_part;
+	mpctx->index = prev_index;
+	mpctx->depth = prev_depth;
+	return ret;
+}
+
+static int
+seach_arg_mime_substring_match(
+	struct search_mimepart_context *mpctx ATTR_UNUSED,
+	const char *key, const char *value)
+{
+	if (value == NULL)
+		return 0;
+
+	/* FIXME: Normalization is required */
+	return (strstr(value, key) != NULL ? 1 : 0);
+}
+
+static int
+seach_arg_mime_envelope_time_match(
+	struct search_mimepart_context *mpctx ATTR_UNUSED,
+	enum mail_search_mime_arg_type type, time_t search_time,
+	const struct message_part_envelope *envelope)
+{
+	time_t sent_time;
+	int timezone_offset;
+
+	if (envelope == NULL)
+		return 0;
+
+	/* NOTE: RFC-3501 specifies that timezone is ignored
+	   in searches. sent_time is returned as UTC, so change it. */
+	// FIXME: adjust comment
+	if (!message_date_parse((const unsigned char *)envelope->date,
+			strlen(envelope->date), &sent_time, &timezone_offset))
+		return 0;
+	sent_time += timezone_offset * 60;
+
+	switch (type) {
+	case SEARCH_MIME_SENTBEFORE:
+		return sent_time < search_time ? 1 : 0;
+	case SEARCH_MIME_SENTON:
+		return (sent_time >= search_time &&
+			sent_time < search_time + 3600*24) ? 1 : 0;
+	case SEARCH_MIME_SENTSINCE:
+		return sent_time >= search_time ? 1 : 0;
+	default:
+		i_unreached();
+	}
+}
+
+static int
+seach_arg_mime_envelope_address_match(
+	struct search_mimepart_context *mpctx ATTR_UNUSED,
+	enum mail_search_mime_arg_type type, const char *key,
+	const struct message_part_envelope *envelope)
+{
+	const struct message_address *addrs;
+	string_t *addrs_enc;
+
+	if (envelope == NULL)
+		return 0;
+
+	switch (type) {
+	case SEARCH_MIME_CC:
+		addrs = envelope->cc;
+		break;
+	case SEARCH_MIME_BCC:
+		addrs = envelope->bcc;
+		break;
+	case SEARCH_MIME_FROM:
+		addrs = envelope->from;
+		break;
+	case SEARCH_MIME_SENDER:
+		addrs = envelope->sender;
+		break;
+	case SEARCH_MIME_REPLY_TO:
+		addrs = envelope->reply_to;
+		break;
+	case SEARCH_MIME_TO:
+		addrs = envelope->to;
+		break;
+	default:
+		i_unreached();
+	}
+
+	/* FIXME: do we need to normalize anything? at least case insensitivity.
+	   MIME header encoding will make this a bit difficult, so it should
+	   probably be normalized directly in the struct message_address. */
+
+	addrs_enc = t_str_new(128);
+	message_address_write(addrs_enc, addrs);
+	return (strstr(str_c(addrs_enc), key) != NULL ? 1 : 0);
+}
+
+static int
+seach_arg_mime_filename_match(struct search_mimepart_context *mpctx,
+				   enum mail_search_mime_arg_type type, const char *key)
+{
+	struct message_part *part = mpctx->mime_part;
+	const char *value;
+	size_t vlen, alen;
+
+	if (!message_part_data_get_filename(part, &value))
+		return 0;
+
+	/* FIXME: Normalization is probably required */
+
+	switch (type) {
+	case SEARCH_MIME_FILENAME_IS:
+		return (strcmp(value, key) == 0 ? 1 : 0);
+	case SEARCH_MIME_FILENAME_CONTAINS:
+		return (strstr(value, key) != NULL ? 1 : 0);
+	case SEARCH_MIME_FILENAME_BEGINS:
+		return (strncmp(value, key, strlen(key)) == 0 ? 1 : 0);
+	case SEARCH_MIME_FILENAME_ENDS:
+		vlen = strlen(value);
+		alen = strlen(key);
+		return (strncmp(value + (vlen - alen), key, alen) == 0 ? 1 : 0);
+	default:
+		break;
+	}
+	i_unreached();
+}
+
+static int
+seach_arg_mime_param_match(const struct message_part_param *params,
+				   unsigned int params_count,
+				   const char *name, const char *key)
+{
+	unsigned int i;
+
+	/* FIXME: Is normalization required? */
+
+	for (i = 0; i < params_count; i++) {
+		if (strcasecmp(params[i].name, name) == 0) {
+			if (key == NULL || *key == '\0')
+				return 1;
+			return (strstr(params[i].value, key) != NULL ? 1 : 0);
+		}
+	}
+	return 0;
+}
+
+static int
+seach_arg_mime_language_match(struct search_mimepart_context *mpctx,
+				   const char *key)
+{
+	struct message_part_data *data = mpctx->mime_part->data;
+	const char *const *lang;
+
+	i_assert(data != NULL);
+
+	lang = data->content_language;
+	if (lang != NULL) {
+		while (*lang != NULL) {
+			/* FIXME: Should use RFC 4647 matching rules */
+			if (strcasecmp(*lang, key) == 0)
+				return 1;
+			lang++;
+		}
+	}
+	return 0;
+}
+
+/* Returns >0 = matched, 0 = not matched (unused), -1 = unknown */
+static int search_mime_arg_match(struct search_mimepart_context *mpctx,
+				   struct mail_search_mime_arg *arg)
+{
+	struct message_part *part = mpctx->mime_part;
+	const struct message_part_data *data = part->data;
+
+	i_assert(data != NULL);
+
+	switch (arg->type) {
+	case SEARCH_MIME_OR:
+	case SEARCH_MIME_SUB:
+		i_unreached();
+
+	case SEARCH_MIME_SIZE_EQUAL:
+		return (part->body_size.virtual_size == arg->value.size ? 1 : 0);
+	case SEARCH_MIME_SIZE_LARGER:
+		return (part->body_size.virtual_size > arg->value.size ? 1 : 0);
+	case SEARCH_MIME_SIZE_SMALLER:
+		return (part->body_size.virtual_size < arg->value.size ? 1 : 0);
+
+	case SEARCH_MIME_DESCRIPTION:
+		return seach_arg_mime_substring_match(mpctx,
+			arg->value.str, data->content_description);
+	case SEARCH_MIME_DISPOSITION_TYPE:
+		return (data->content_disposition != NULL &&
+			strcasecmp(data->content_disposition,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_DISPOSITION_PARAM:
+		return seach_arg_mime_param_match
+			(data->content_disposition_params,
+				data->content_disposition_params_count,
+				arg->field_name, arg->value.str);
+	case SEARCH_MIME_ENCODING:
+		return (data->content_transfer_encoding != NULL &&
+			strcasecmp(data->content_transfer_encoding,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_ID:
+		return (data->content_id != NULL &&
+			strcasecmp(data->content_id,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_LANGUAGE:
+		return seach_arg_mime_language_match(mpctx, arg->value.str);
+	case SEARCH_MIME_LOCATION:
+		return (data->content_location != NULL &&
+			strcasecmp(data->content_location,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_MD5:
+		return (data->content_md5 != NULL &&
+			strcmp(data->content_md5,
+				arg->value.str) == 0 ? 1 : 0);
+
+	case SEARCH_MIME_TYPE:
+		return (data->content_type != NULL &&
+			strcasecmp(data->content_type,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_SUBTYPE:
+		return (data->content_subtype != NULL &&
+			strcasecmp(data->content_subtype,
+				arg->value.str) == 0 ? 1 : 0);
+	case SEARCH_MIME_PARAM:
+		return seach_arg_mime_param_match
+			(data->content_type_params,
+				data->content_type_params_count,
+				arg->field_name, arg->value.str);
+
+	case SEARCH_MIME_SENTBEFORE:
+	case SEARCH_MIME_SENTON:
+	case SEARCH_MIME_SENTSINCE:
+		return seach_arg_mime_envelope_time_match
+			(mpctx, arg->type, arg->value.time, data->envelope);
+
+	case SEARCH_MIME_CC:
+	case SEARCH_MIME_BCC:
+	case SEARCH_MIME_FROM:
+	case SEARCH_MIME_REPLY_TO:
+	case SEARCH_MIME_SENDER:
+	case SEARCH_MIME_TO:
+		return seach_arg_mime_envelope_address_match
+			(mpctx, arg->type, arg->value.str, data->envelope);
+
+	case SEARCH_MIME_SUBJECT:
+		if (data->envelope == NULL)
+			return 0;
+		return seach_arg_mime_substring_match(mpctx,
+			arg->value.str, data->envelope->subject);
+	case SEARCH_MIME_IN_REPLY_TO:
+		if (data->envelope == NULL)
+			return 0;
+		return seach_arg_mime_substring_match(mpctx,
+			arg->value.str, data->envelope->in_reply_to);
+	case SEARCH_MIME_MESSAGE_ID:
+		if (data->envelope == NULL)
+			return 0;
+		return seach_arg_mime_substring_match(mpctx,
+			arg->value.str, data->envelope->message_id);
+
+	case SEARCH_MIME_DEPTH_EQUAL:
+		return (mpctx->depth == arg->value.number ? 1 : 0);
+	case SEARCH_MIME_DEPTH_MIN:
+		return (mpctx->depth >= arg->value.number ? 1 : 0);
+	case SEARCH_MIME_DEPTH_MAX:
+		return (mpctx->depth <= arg->value.number ? 1 : 0);
+	case SEARCH_MIME_INDEX:
+		return (mpctx->index == arg->value.number ? 1 : 0);
+
+	case SEARCH_MIME_PARENT:
+		return seach_arg_mime_parent_match(mpctx, arg);
+	case SEARCH_MIME_CHILD:
+		return seach_arg_mime_child_match(mpctx, arg);
+
+	case SEARCH_MIME_FILENAME_IS:
+	case SEARCH_MIME_FILENAME_CONTAINS:
+	case SEARCH_MIME_FILENAME_BEGINS:
+	case SEARCH_MIME_FILENAME_ENDS:
+		return seach_arg_mime_filename_match(mpctx,
+			arg->type, arg->value.str);
+
+	case SEARCH_MIME_HEADER:
+	case SEARCH_MIME_BODY:
+	case SEARCH_MIME_TEXT:
+		break;
+	}
+	return -1;
+}
+
+static void search_mime_arg(struct mail_search_mime_arg *arg,
+			      struct search_mimepart_context *mpctx)
+{
+	switch (search_mime_arg_match(mpctx, arg)) {
+	case -1:
+		/* unknown */
+		break;
+	case 0:
+		ARG_SET_RESULT(arg, 0);
+		break;
+	default:
+		ARG_SET_RESULT(arg, 1);
+		break;
+	}
+}
+
+static int seach_arg_mime_parts_match(struct search_mimepart_context *mpctx,
+				   struct mail_search_mime_arg *args,
+				   struct message_part *parts)
+{
+	struct message_part *part;
+	struct search_mimepart_stack *level;
+	int ret;
+
+	level = array_append_space(&mpctx->stack);
+	level->index = 1;
+
+	part = parts;
+	while (part != NULL) {
+		mpctx->mime_part = part;
+		mail_search_mime_args_reset(args, TRUE);
+
+		mpctx->index = level->index;
+		mpctx->depth = array_count(&mpctx->stack)-1;
+
+		if ((ret=mail_search_mime_args_foreach
+			(args, search_mime_arg, mpctx)) != 0)
+			return ret;
+		if (part->children != NULL) {
+			level = array_append_space(&mpctx->stack);
+			level->index = 1;
+			part = part->children;
+		} else {
+			while (part->next == NULL) {
+				if (part->parent == NULL)
+					break;
+				array_delete(&mpctx->stack, array_count(&mpctx->stack)-1, 1);
+				level = array_idx_modifiable
+					(&mpctx->stack, array_count(&mpctx->stack)-1);
+				part = part->parent;
+			}
+			level->index++;
+			part = part->next;
+		}
+	}
+
+	return 0;
+}
+
+/* Returns >0 = matched, 0 = not matched, -1 = unknown */
+static int search_arg_match_mimepart(struct search_mimepart_context *mpctx,
+				   struct mail_search_arg *arg)
+{
+	struct index_search_context *ctx = mpctx->index_ctx;
+	const char *bodystructure, *error;
+
+	if (arg->type != SEARCH_MIMEPART)
+		return -1;
+
+	if (mpctx->pool == NULL) {
+		mpctx->pool = pool_alloconly_create
+			(MEMPOOL_GROWING"search mime parts", 4096);
+		p_array_init(&mpctx->stack, mpctx->pool, 16);
+	}
+	if (mpctx->mime_parts == NULL) {
+		/* FIXME: could the mail object already have message_part tree with
+		   data? */
+		if (mail_get_special(ctx->cur_mail,
+			MAIL_FETCH_IMAP_BODYSTRUCTURE, &bodystructure) < 0)
+			return -1;
+		if (imap_bodystructure_parse_full(bodystructure, mpctx->pool,
+			&mpctx->mime_parts, &error) < 0)
+			return -1;
+	}
+
+	/* FIXME: implement HEADER, BODY and TEXT (not from BODYSTRUCTURE)
+	   Needs to support FTS */
+	return seach_arg_mime_parts_match
+		(mpctx, arg->value.mime_part->args, mpctx->mime_parts);
+}
+
+static void search_mimepart_arg(struct mail_search_arg *arg,
+			      struct search_mimepart_context *mpctx)
+{
+	switch (search_arg_match_mimepart(mpctx, arg)) {
+	case -1:
+		/* unknown */
+		break;
+	case 0:
+		ARG_SET_RESULT(arg, 0);
+		break;
+	default:
+		ARG_SET_RESULT(arg, 1);
+		break;
+	}
+}
+
+int index_search_mime_arg_match(struct mail_search_arg *args,
+	struct index_search_context *ctx)
+{
+	struct search_mimepart_context mpctx;
+	int ret;
+
+	i_zero(&mpctx);
+	mpctx.index_ctx = ctx;
+
+	ret = mail_search_args_foreach(args,
+				       search_mimepart_arg, &mpctx);
+
+	if (mpctx.pool != NULL)
+		pool_unref(&mpctx.pool);
+	return ret;
+}
+
--- a/src/lib-storage/index/index-search-private.h	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/index/index-search-private.h	Wed May 25 01:57:08 2016 +0200
@@ -5,6 +5,9 @@
 
 #include <sys/time.h>
 
+struct mail_search_mime_part;
+struct imap_message_part;
+
 struct index_search_context {
         struct mail_search_context mail_ctx;
 	struct mail_index_view *view;
@@ -37,4 +40,7 @@
 
 struct mail *index_search_get_mail(struct index_search_context *ctx);
 
+int index_search_mime_arg_match(struct mail_search_arg *args,
+	struct index_search_context *ctx);
+
 #endif
--- a/src/lib-storage/index/index-search.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/index/index-search.c	Wed May 25 01:57:08 2016 +0200
@@ -1378,6 +1378,8 @@
 				       search_cached_arg, ctx);
 	if (ret < 0)
 		ret = search_arg_match_text(ctx->mail_ctx.args->args, ctx);
+	if (ret < 0)
+		ret = index_search_mime_arg_match(ctx->mail_ctx.args->args, ctx);
 	return ret;
 }
 
@@ -1420,6 +1422,7 @@
 	case SEARCH_MAILBOX_GUID:
 	case SEARCH_MAILBOX_GLOB:
 	case SEARCH_REAL_UID:
+	case SEARCH_MIMEPART:
 		return TRUE;
 	}
 	return FALSE;
--- a/src/lib-storage/mail-search-args-cmdline.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-search-args-cmdline.c	Wed May 25 01:57:08 2016 +0200
@@ -79,6 +79,7 @@
 	case SEARCH_GUID:
 	case SEARCH_MAILBOX_GLOB:
 	case SEARCH_REAL_UID:
+	case SEARCH_MIMEPART:
 		break;
 	}
 	new_arg = *arg;
--- a/src/lib-storage/mail-search-args-imap.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-search-args-imap.c	Wed May 25 01:57:08 2016 +0200
@@ -10,6 +10,7 @@
 #include "imap-util.h"
 #include "imap-quote.h"
 #include "mail-search.h"
+#include "mail-search-mime.h"
 
 #include <time.h>
 
@@ -285,6 +286,12 @@
 		str_append(dest, "X-REAL-UID ");
 		imap_write_seq_range(dest, &arg->value.seqset);
 		break;
+	case SEARCH_MIMEPART:
+		str_append(dest, "MIMEPART ");
+		if (!mail_search_mime_part_to_imap(dest,
+			arg->value.mime_part, error_r))
+			return FALSE;
+		break;
 	}
 	return TRUE;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime-build.c	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,173 @@
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "charset-utf8.h"
+#include "mail-storage-private.h"
+#include "mail-search-parser.h"
+#include "mail-search-mime-register.h"
+#include "mail-search-mime-build.h"
+
+static int mail_search_mime_build_list(struct mail_search_mime_build_context *ctx,
+				  struct mail_search_mime_arg **arg_r);
+
+struct mail_search_mime_arg *
+mail_search_mime_build_new(struct mail_search_mime_build_context *ctx,
+		      enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *arg;
+
+	arg = p_new(ctx->ctx->pool, struct mail_search_mime_arg, 1);
+	arg->type = type;
+	return arg;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_build_str(struct mail_search_mime_build_context *ctx,
+		      enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *sarg;
+	const char *value;
+
+	sarg = mail_search_mime_build_new(ctx, type);
+	if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+		return NULL;
+	sarg->value.str = p_strdup(ctx->ctx->pool, value);
+	return sarg;
+}
+
+static int
+mail_search_mime_build_key_int(struct mail_search_mime_build_context *ctx,
+			  struct mail_search_mime_arg *parent,
+			  struct mail_search_mime_arg **arg_r)
+{
+	struct mail_search_mime_arg *sarg;
+	struct mail_search_mime_arg *old_parent = ctx->parent;
+	const char *key;
+	const struct mail_search_mime_register_arg *reg_arg;
+	int ret;
+
+	ctx->parent = parent;
+
+	if ((ret = mail_search_parse_key(ctx->ctx->parser, &key)) <= 0)
+		return ret;
+
+	if (strcmp(key, MAIL_SEARCH_PARSER_KEY_LIST) == 0) {
+		if (mail_search_mime_build_list(ctx, &sarg) < 0)
+			return -1;
+		if (sarg->value.subargs == NULL) {
+			ctx->ctx->_error = "No MIMEPART keys inside list";
+			return -1;
+		}
+
+		ctx->parent = old_parent;
+		*arg_r = sarg;
+		return 1;
+	}
+	key = t_str_ucase(key);
+
+	reg_arg = mail_search_mime_register_find(key);
+	if (reg_arg != NULL)
+		sarg = reg_arg->build(ctx);
+	else {
+		sarg = NULL;
+		ctx->ctx->_error = p_strconcat
+			(ctx->ctx->pool, "Unknown MIMEPART key ", key, NULL);
+	}
+
+	ctx->parent = old_parent;
+	*arg_r = sarg;
+	return sarg == NULL ? -1 : 1;
+}
+
+int mail_search_mime_build_key(struct mail_search_mime_build_context *ctx,
+			  struct mail_search_mime_arg *parent,
+			  struct mail_search_mime_arg **arg_r)
+{
+	int ret;
+
+	ret = mail_search_mime_build_key_int(ctx, parent, arg_r);
+	if (ret <= 0) {
+		if (ret == 0)
+			ctx->ctx->_error = "Missing MIMEPART key";
+		return -1;
+	}
+	return 0;
+}
+
+static int mail_search_mime_build_list(struct mail_search_mime_build_context *ctx,
+				  struct mail_search_mime_arg **arg_r)
+{
+	struct mail_search_mime_arg *sarg, **subargs;
+	enum mail_search_mime_arg_type cur_type = SEARCH_MIME_SUB;
+	int ret;
+
+	sarg = p_new(ctx->ctx->pool, struct mail_search_mime_arg, 1);
+	sarg->type = cur_type;
+
+	subargs = &sarg->value.subargs;
+	while ((ret = mail_search_mime_build_key_int(ctx, sarg, subargs)) > 0) {
+		if (cur_type == sarg->type) {
+			/* expected type */
+		} else if (cur_type == SEARCH_MIME_SUB) {
+			/* type changed. everything in this list must now
+			   belong to this type. */
+			cur_type = sarg->type;
+		} else {
+			ctx->ctx->_error =
+				"Use parenthesis when mixing ANDs and ORs";
+			return -1;
+		}
+		subargs = &(*subargs)->next;
+		sarg->type = SEARCH_MIME_SUB;
+	}
+	if (ret < 0)
+		return -1;
+	sarg->type = cur_type;
+	*arg_r = sarg;
+	return 0;
+}
+
+int mail_search_mime_build(struct mail_search_build_context *bctx,
+		      struct mail_search_mime_part **mpart_r)
+{
+  struct mail_search_mime_build_context ctx;
+	struct mail_search_mime_part *mpart;
+	struct mail_search_mime_arg *root;
+	int ret;
+
+	*mpart_r = NULL;
+
+	i_zero(&ctx);
+	ctx.ctx = bctx;
+	ctx.mime_part = mpart =
+		p_new(bctx->pool, struct mail_search_mime_part, 1);
+
+	if ((ret=mail_search_mime_build_key(&ctx, NULL, &root)) < 0)
+		return ret;
+
+	if (root->type == SEARCH_MIME_SUB && !root->match_not) {
+		/* simple SUB root */
+		mpart->args = root->value.subargs;
+	} else {
+		mpart->args = root;
+	}
+
+	*mpart_r = mpart;
+	return 0;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_build_add(pool_t pool,
+		      struct mail_search_mime_part *mpart,
+		      enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *arg;
+
+	arg = p_new(pool, struct mail_search_mime_arg, 1);
+	arg->type = type;
+
+	arg->next = mpart->args;
+	mpart->args = arg;
+	return arg;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime-build.h	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,44 @@
+#ifndef MAIL_SEARCH_MIME_BUILD_H
+#define	MAIL_SEARCH_MIME_BUILD_H
+
+#include "mail-search.h"
+#include "mail-search-build.h"
+#include "mail-search-register.h"
+#include "mail-search-mime.h"
+
+struct mailbox;
+
+struct mail_search_mime_build_context {
+	struct mail_search_build_context *ctx;
+	struct mail_search_mime_part *mime_part;
+
+	struct mail_search_mime_arg *parent;
+};
+
+/* Start building a new MIMPART search key. Use mail_search_mime_args_unref()
+   to free it. */
+struct mail_search_mime_part *mail_search_mime_build_init(void);
+
+/* Convert IMAP SEARCH command compatible parameters to
+   mail_search_mime_args. */
+int mail_search_mime_build(struct mail_search_build_context *bctx,
+		      struct mail_search_mime_part **mpart_r);
+
+/* Add new search arg with given type. */
+struct mail_search_mime_arg *
+mail_search_mime_build_add(pool_t pool,
+		      struct mail_search_mime_part *mpart,
+		      enum mail_search_mime_arg_type type);
+
+struct mail_search_mime_arg *
+mail_search_mime_build_new(struct mail_search_mime_build_context *ctx,
+		      enum mail_search_mime_arg_type type);
+struct mail_search_mime_arg *
+mail_search_mime_build_str(struct mail_search_mime_build_context *ctx,
+		      enum mail_search_mime_arg_type type);
+/* Returns 0 if arg is returned, -1 if error. */
+int mail_search_mime_build_key(struct mail_search_mime_build_context *ctx,
+			  struct mail_search_mime_arg *parent,
+			  struct mail_search_mime_arg **arg_r);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime-register.c	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,547 @@
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "imap-date.h"
+#include "imap-seqset.h"
+#include "imap-utf7.h"
+#include "imap-util.h"
+#include "mail-search-parser.h"
+#include "mail-search-mime-register.h"
+#include "mail-search-mime-build.h"
+#include "mail-search-mime.h"
+
+struct mail_search_mime_register {
+	ARRAY(struct mail_search_mime_register_arg) args;
+
+	bool args_sorted:1;
+};
+
+struct mail_search_mime_register *mail_search_mime_register = NULL;
+
+static void
+mail_search_register_add_default(void);
+
+/*
+ * Register
+ */
+
+static struct mail_search_mime_register *
+mail_search_mime_register_init(void)
+{
+	struct mail_search_mime_register *reg =
+		mail_search_mime_register;
+	if (reg == NULL) {
+		reg = i_new(struct mail_search_mime_register, 1);
+		i_array_init(&reg->args, 64);
+
+		mail_search_mime_register = reg;
+		mail_search_register_add_default();
+	}
+
+	return reg;
+}
+
+void mail_search_mime_register_deinit(void)
+{
+	struct mail_search_mime_register *reg =
+		mail_search_mime_register;
+
+	mail_search_mime_register = NULL;
+	if (reg == NULL)
+		return;
+
+	array_free(&reg->args);
+	i_free(reg);
+}
+
+void mail_search_mime_register_add(
+			      const struct mail_search_mime_register_arg *arg,
+			      unsigned int count)
+{
+	struct mail_search_mime_register *reg =
+		mail_search_mime_register_init();
+
+	array_append(&reg->args, arg, count);
+	reg->args_sorted = FALSE;
+}
+
+static int
+mail_search_mime_register_arg_cmp(
+	const struct mail_search_mime_register_arg *arg1,
+	const struct mail_search_mime_register_arg *arg2)
+{
+	return strcmp(arg1->key, arg2->key);
+}
+
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_get(unsigned int *count_r)
+{
+	struct mail_search_mime_register *reg =
+		mail_search_mime_register_init();
+
+	if (!reg->args_sorted) {
+		array_sort(&reg->args, mail_search_mime_register_arg_cmp);
+		reg->args_sorted = TRUE;
+	}
+
+	return array_get(&reg->args, count_r);
+}
+
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_find(const char *key)
+{
+	struct mail_search_mime_register_arg arg;
+	struct mail_search_mime_register *reg =
+		mail_search_mime_register_init();
+
+	if (!reg->args_sorted) {
+		array_sort(&reg->args, mail_search_mime_register_arg_cmp);
+		reg->args_sorted = TRUE;
+	}
+
+	arg.key = key;
+	return array_bsearch(&reg->args, &arg, mail_search_mime_register_arg_cmp);
+}
+
+/*
+ * Default MIMEPART args
+ */
+
+static struct mail_search_mime_arg *
+mail_search_mime_not(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+
+	if (mail_search_mime_build_key(ctx, ctx->parent, &smarg) < 0)
+		return NULL;
+
+	smarg->match_not = !smarg->match_not;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_or(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg, **subargs;
+
+	/* <search-key1> <search-key2> */
+	smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_OR);
+
+	subargs = &smarg->value.subargs;
+	do {
+		if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
+			return NULL;
+		subargs = &(*subargs)->next;
+
+		/* <key> OR <key> OR ... <key> - put them all
+		   under one SEARCH_MIME_OR list. */
+	} while (mail_search_parse_skip_next(ctx->ctx->parser, "OR"));
+
+	if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
+		return NULL;
+	return smarg;
+}
+
+#define CALLBACK_STR(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+	return mail_search_mime_build_str(ctx, _type); \
+}
+
+static struct mail_search_mime_arg *
+arg_new_date(struct mail_search_mime_build_context *ctx,
+	     enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *smarg;
+	const char *value;
+
+	smarg = mail_search_mime_build_new(ctx, type);
+	if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+		return NULL;
+	if (!imap_parse_date(value, &smarg->value.time)) {
+		ctx->ctx->_error = "Invalid search date parameter";
+		return NULL;
+	}
+	return smarg;
+}
+
+#define CALLBACK_DATE(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+	return arg_new_date(ctx, _type); \
+}
+CALLBACK_DATE(sentbefore, SEARCH_MIME_SENTBEFORE)
+CALLBACK_DATE(senton, SEARCH_MIME_SENTON)
+CALLBACK_DATE(sentsince, SEARCH_MIME_SENTSINCE)
+
+static struct mail_search_mime_arg *
+mail_search_mime_size(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+	enum mail_search_mime_arg_type type;
+	const char *key, *value;
+	uoff_t size;
+
+	if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+		ctx->ctx->_error = "Invalid MIMEPART SIZE key type";
+		return NULL;
+	}
+
+	key = t_str_ucase(key);
+	if (strcmp(key, "LARGER") == 0)
+		type = SEARCH_MIME_SIZE_LARGER;
+	else 	if (strcmp(key, "SMALLER") == 0)
+		type = SEARCH_MIME_SIZE_SMALLER;
+	else {
+		type = SEARCH_MIME_SIZE_EQUAL;
+		value = key;
+	}
+
+	if (type != SEARCH_MIME_SIZE_EQUAL &&
+		mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART SIZE value";
+		return NULL;
+	}
+
+	if (str_to_uoff(value, &size) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART SIZE value";
+		return NULL;
+	}
+
+	smarg = mail_search_mime_build_new(ctx, type);
+	smarg->value.size = size;
+	return smarg;
+}
+
+CALLBACK_STR(description, SEARCH_MIME_DESCRIPTION)
+CALLBACK_STR(encoding, SEARCH_MIME_ENCODING)
+CALLBACK_STR(id, SEARCH_MIME_ID)
+CALLBACK_STR(language, SEARCH_MIME_LANGUAGE)
+CALLBACK_STR(location, SEARCH_MIME_LOCATION)
+CALLBACK_STR(md5, SEARCH_MIME_MD5)
+
+CALLBACK_STR(type, SEARCH_MIME_TYPE)
+CALLBACK_STR(subtype, SEARCH_MIME_SUBTYPE)
+
+CALLBACK_STR(bcc, SEARCH_MIME_BCC)
+CALLBACK_STR(cc, SEARCH_MIME_CC)
+CALLBACK_STR(from, SEARCH_MIME_FROM)
+CALLBACK_STR(in_reply_to, SEARCH_MIME_IN_REPLY_TO)
+CALLBACK_STR(message_id, SEARCH_MIME_MESSAGE_ID)
+CALLBACK_STR(reply_to, SEARCH_MIME_REPLY_TO)
+CALLBACK_STR(sender, SEARCH_MIME_SENDER)
+CALLBACK_STR(subject, SEARCH_MIME_SUBJECT)
+CALLBACK_STR(to, SEARCH_MIME_TO)
+
+static struct mail_search_mime_arg *
+arg_new_field(struct mail_search_mime_build_context *ctx,
+	enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *smarg;
+	const char *field_name, *value;
+
+	/* <field-name> <string> */
+	if (mail_search_parse_string(ctx->ctx->parser, &field_name) < 0)
+		return NULL;
+	if (mail_search_build_get_utf8(ctx->ctx, field_name, &field_name) < 0)
+		return NULL;
+	if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+		return NULL;
+	if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0)
+		return NULL;
+
+	smarg = mail_search_mime_build_new(ctx, type);
+	smarg->field_name = str_ucase(p_strdup(ctx->ctx->pool, field_name));
+	smarg->value.str = value;
+
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_param(struct mail_search_mime_build_context *ctx)
+{
+	return arg_new_field
+		(ctx, SEARCH_MIME_PARAM);
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_header(struct mail_search_mime_build_context *ctx)
+{
+	return arg_new_field
+		(ctx, SEARCH_MIME_HEADER);
+}
+
+static struct mail_search_mime_arg *
+arg_new_body(struct mail_search_mime_build_context *ctx,
+	     enum mail_search_mime_arg_type type)
+{
+	struct mail_search_mime_arg *smarg;
+
+	smarg = mail_search_mime_build_str(ctx, type);
+	if (smarg == NULL)
+		return NULL;
+
+	if (mail_search_build_get_utf8(ctx->ctx, smarg->value.str,
+				       &smarg->value.str) < 0)
+		return NULL;
+	return smarg;
+}
+
+#define CALLBACK_BODY(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+	return arg_new_body(ctx, _type); \
+}
+CALLBACK_BODY(body, SEARCH_MIME_BODY)
+CALLBACK_BODY(text, SEARCH_MIME_TEXT)
+
+static struct mail_search_mime_arg *
+mail_search_mime_disposition(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+	const char *key, *value;
+
+	if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+		ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
+		return NULL;
+	}
+
+	key = t_str_ucase(key);
+	if (strcmp(key, "TYPE") == 0) {
+		if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+			ctx->ctx->_error = "Invalid MIMEPART DISPOSITION TYPE value";
+			return NULL;
+		}
+		smarg = mail_search_mime_build_new
+			(ctx, SEARCH_MIME_DISPOSITION_TYPE);
+		smarg->value.str = p_strdup(ctx->ctx->pool, value);
+		return smarg;
+	} else 	if (strcmp(key, "PARAM") == 0) {
+		return arg_new_field
+			(ctx, SEARCH_MIME_DISPOSITION_PARAM);
+	}
+
+	ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
+	return NULL;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_depth(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+	enum mail_search_mime_arg_type type;
+	const char *key, *value;
+	unsigned int depth;
+
+	if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+		ctx->ctx->_error = "Invalid MIMEPART DEPTH key";
+		return NULL;
+	}
+
+	key = t_str_ucase(key);
+	if (strcmp(key, "MIN") == 0)
+		type = SEARCH_MIME_DEPTH_MIN;
+	else 	if (strcmp(key, "MAX") == 0)
+		type = SEARCH_MIME_DEPTH_MAX;
+	else {
+		type = SEARCH_MIME_DEPTH_EQUAL;
+		value = key;
+	}
+
+	if (type != SEARCH_MIME_DEPTH_EQUAL &&
+		mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART DEPTH value";
+		return NULL;
+	}
+
+	if (str_to_uint(value, &depth) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART DEPTH level";
+		return NULL;
+	}
+
+	smarg = mail_search_mime_build_new(ctx, type);
+	smarg->value.number = depth;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_index(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+	const char *value;
+	unsigned int index;
+
+	if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART INDEX value";
+		return NULL;
+	}
+
+	if (str_to_uint(value, &index) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART INDEX number";
+		return NULL;
+	}
+
+	smarg = mail_search_mime_build_new
+		(ctx, SEARCH_MIME_INDEX);
+	smarg->value.number = index;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_filename(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg;
+	enum mail_search_mime_arg_type type;
+	const char *key, *value;
+
+	if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+		ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
+		return NULL;
+	}
+
+	key = t_str_ucase(key);
+	if (strcmp(key, "IS") == 0)
+		type = SEARCH_MIME_FILENAME_IS;
+	else 	if (strcmp(key, "CONTAINS") == 0)
+		type = SEARCH_MIME_FILENAME_CONTAINS;
+	else 	if (strcmp(key, "BEGINS") == 0)
+		type = SEARCH_MIME_FILENAME_BEGINS;
+	else 	if (strcmp(key, "ENDS") == 0)
+		type = SEARCH_MIME_FILENAME_ENDS;
+	else {
+		ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
+		return NULL;
+	}
+
+	if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART FILENAME string value";
+		return NULL;
+	}
+
+	if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0) {
+		ctx->ctx->_error = "Invalid MIMEPART FILENAME stromg value";
+		return NULL;
+	}
+
+	smarg = mail_search_mime_build_new(ctx, type);
+	smarg->value.str = value;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_parent(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg, *subargs;
+
+	smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_PARENT);
+	if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
+		return NULL;
+	if (subargs == smarg)
+		smarg->value.subargs = NULL;
+	else if (subargs->type == SEARCH_MIME_SUB)
+		smarg->value.subargs = subargs->value.subargs;
+	else
+		smarg->value.subargs = subargs;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_child(struct mail_search_mime_build_context *ctx)
+{
+	struct mail_search_mime_arg *smarg, *subargs;
+
+	smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_CHILD);
+	if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
+		return NULL;
+	if (subargs == smarg)
+		smarg->value.subargs = NULL;
+	else if (subargs->type == SEARCH_MIME_SUB)
+		smarg->value.subargs = subargs->value.subargs;
+	else
+		smarg->value.subargs = subargs;
+	return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_exists(struct mail_search_mime_build_context *ctx)
+{
+	if (ctx->parent == NULL ||
+		(ctx->parent->type != SEARCH_MIME_PARENT &&
+			ctx->parent->type != SEARCH_MIME_CHILD)) {
+		ctx->ctx->_error = "EXISTS key can only be used with PARENT or CHILD";
+		return NULL;
+	}
+	return ctx->parent;
+}
+
+static const struct mail_search_mime_register_arg
+mime_register_args[] = {
+	/* argument set operations */
+	{ "NOT", mail_search_mime_not },
+	{ "OR", mail_search_mime_or },
+
+	/* dates */
+	{ "SENTBEFORE", mail_search_mime_sentbefore },
+	{ "SENTON", mail_search_mime_senton },
+	{ "SENTSINCE", mail_search_mime_sentsince },
+
+	/* size */
+	{ "SIZE", mail_search_mime_size },
+
+	/* part properties */
+	{ "DESCRIPTION", mail_search_mime_description },
+	{ "DISPOSITION", mail_search_mime_disposition },
+	{ "ENCODING", mail_search_mime_encoding },
+	{ "ID", mail_search_mime_id },
+	{ "LANGUAGE", mail_search_mime_language },
+	{ "LOCATION", mail_search_mime_location },
+	{ "MD5", mail_search_mime_md5 },
+
+	/* content-type */
+	{ "TYPE", mail_search_mime_type },
+	{ "SUBTYPE", mail_search_mime_subtype },
+	{ "PARAM", mail_search_mime_param },
+
+	/* headers */
+	{ "HEADER", mail_search_mime_header },
+
+	/* message */
+	{ "BCC", mail_search_mime_bcc },
+	{ "CC", mail_search_mime_cc },
+	{ "FROM", mail_search_mime_from },
+	{ "IN-REPLY-TO", mail_search_mime_in_reply_to },
+	{ "MESSAGE-ID", mail_search_mime_message_id },
+	{ "REPLY-TO", mail_search_mime_reply_to },
+	{ "SENDER", mail_search_mime_sender },
+	{ "SUBJECT", mail_search_mime_subject },
+	{ "TO", mail_search_mime_to },
+
+	/* body */
+	{ "BODY", mail_search_mime_body },
+	{ "TEXT", mail_search_mime_text },
+
+	/* position */
+	{ "DEPTH", mail_search_mime_depth },
+	{ "INDEX", mail_search_mime_index },
+
+	/* relations */
+	{ "PARENT", mail_search_mime_parent },
+	{ "CHILD", mail_search_mime_child },
+	{ "EXISTS", mail_search_mime_exists },
+
+	/* filename */
+	{ "FILENAME", mail_search_mime_filename },
+};
+
+static void
+mail_search_register_add_default(void)
+{
+	mail_search_mime_register_add(mime_register_args,
+			 N_ELEMENTS(mime_register_args));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime-register.h	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,30 @@
+#ifndef MAIL_SEARCH_MIME_REGISTER_H
+#define MAIL_SEARCH_MIME_REGISTER_H
+
+struct mail_search_mime_arg;
+struct mail_search_mime_build_context;
+
+struct mail_search_mime_register_arg {
+	const char *key;
+
+	/* returns parsed arg or NULL if error. error message is set to ctx->ctx. */
+	struct mail_search_mime_arg *
+		(*build)(struct mail_search_mime_build_context *ctx);
+};
+
+void mail_search_mime_register_deinit(void);
+
+void mail_search_mime_register_add(
+			      const struct mail_search_mime_register_arg *arg,
+			      unsigned int count);
+
+/* Return all registered args sorted. */
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_get(unsigned int *count_r);
+
+/* Find key's registered arg, or NULL if not found. */
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_find(const char *key);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime.c	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,612 @@
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "utc-offset.h"
+#include "imap-date.h"
+#include "imap-util.h"
+#include "imap-quote.h"
+#include "mail-search.h"
+#include "mail-search-mime.h"
+
+/*
+ *
+ */
+
+static struct mail_search_mime_arg *
+mail_search_mime_arg_dup_one(pool_t pool,
+	const struct mail_search_mime_arg *arg)
+{
+	struct mail_search_mime_arg *new_arg;
+
+	new_arg = p_new(pool, struct mail_search_mime_arg, 1);
+	new_arg->type = arg->type;
+	new_arg->match_not = arg->match_not;
+	new_arg->match_always = arg->match_always;
+	new_arg->nonmatch_always = arg->nonmatch_always;
+
+	switch (arg->type) {
+	case SEARCH_MIME_OR:
+	case SEARCH_MIME_SUB:
+		new_arg->value.subargs =
+			mail_search_mime_arg_dup(pool, arg->value.subargs);
+		break;
+	case SEARCH_MIME_SIZE_EQUAL:
+	case SEARCH_MIME_SIZE_LARGER:
+	case SEARCH_MIME_SIZE_SMALLER:
+		new_arg->value.size = arg->value.size;
+		break;
+	case SEARCH_MIME_HEADER:
+		new_arg->field_name = p_strdup(pool, arg->field_name);
+		/* fall through */
+	case SEARCH_MIME_DESCRIPTION:
+	case SEARCH_MIME_DISPOSITION_TYPE:
+	case SEARCH_MIME_DISPOSITION_PARAM:
+	case SEARCH_MIME_ENCODING:
+	case SEARCH_MIME_ID:
+	case SEARCH_MIME_LANGUAGE:
+	case SEARCH_MIME_LOCATION:
+	case SEARCH_MIME_MD5:
+	case SEARCH_MIME_TYPE:
+	case SEARCH_MIME_SUBTYPE:
+	case SEARCH_MIME_PARAM:
+	case SEARCH_MIME_BODY:
+	case SEARCH_MIME_TEXT:
+	case SEARCH_MIME_CC:
+	case SEARCH_MIME_BCC:
+	case SEARCH_MIME_FROM:
+	case SEARCH_MIME_IN_REPLY_TO:
+	case SEARCH_MIME_MESSAGE_ID:
+	case SEARCH_MIME_REPLY_TO:
+	case SEARCH_MIME_SENDER:
+	case SEARCH_MIME_SUBJECT:
+	case SEARCH_MIME_TO:
+	case SEARCH_MIME_FILENAME_IS:
+	case SEARCH_MIME_FILENAME_CONTAINS:
+	case SEARCH_MIME_FILENAME_BEGINS:
+	case SEARCH_MIME_FILENAME_ENDS:
+		new_arg->value.str =
+			p_strdup(pool, arg->value.str);
+		break;
+	case SEARCH_MIME_SENTBEFORE:
+	case SEARCH_MIME_SENTON:
+	case SEARCH_MIME_SENTSINCE:
+		new_arg->value.time = arg->value.time;
+		break;
+	case SEARCH_MIME_PARENT:
+	case SEARCH_MIME_CHILD:
+		if (new_arg->value.subargs != NULL) {
+			new_arg->value.subargs =
+				mail_search_mime_arg_dup(pool, arg->value.subargs);
+		}
+		break;
+	case SEARCH_MIME_DEPTH_EQUAL:
+	case SEARCH_MIME_DEPTH_MIN:
+	case SEARCH_MIME_DEPTH_MAX:
+	case SEARCH_MIME_INDEX:
+		new_arg->value.number = arg->value.number;
+		break;
+	}
+	return new_arg;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_arg_dup(pool_t pool,
+	const struct mail_search_mime_arg *arg)
+{
+	struct mail_search_mime_arg *new_arg = NULL, **dest = &new_arg;
+
+	for (; arg != NULL; arg = arg->next) {
+		*dest = mail_search_mime_arg_dup_one(pool, arg);
+		dest = &(*dest)->next;
+	}
+	return new_arg;
+}
+
+struct mail_search_mime_part *
+mail_search_mime_part_dup(pool_t pool,
+	const struct mail_search_mime_part *mpart)
+{
+	struct mail_search_mime_part *new_mpart;
+
+	new_mpart = p_new(pool, struct mail_search_mime_part, 1);
+	new_mpart->simplified = mpart->simplified;
+	new_mpart->args = mail_search_mime_arg_dup(pool, mpart->args);
+	return new_mpart;
+}
+
+/*
+ *
+ */
+
+void mail_search_mime_args_reset(struct mail_search_mime_arg *args,
+	bool full_reset)
+{
+	while (args != NULL) {
+		if (args->type == SEARCH_MIME_OR || args->type == SEARCH_MIME_SUB)
+			mail_search_mime_args_reset(args->value.subargs, full_reset);
+
+		if (args->match_always) {
+			if (!full_reset)
+				args->result = 1;
+			else {
+				args->match_always = FALSE;
+				args->result = -1;
+			}
+		} else if (args->nonmatch_always) {
+			if (!full_reset)
+				args->result = 0;
+			else {
+				args->nonmatch_always = FALSE;
+				args->result = -1;
+			}
+		} else {
+			args->result = -1;
+		}
+
+		args = args->next;
+	}
+}
+
+static void search_mime_arg_foreach(struct mail_search_mime_arg *arg,
+			       mail_search_mime_foreach_callback_t *callback,
+			       void *context)
+{
+	struct mail_search_mime_arg *subarg;
+
+	if (arg->result != -1)
+		return;
+
+	if (arg->type == SEARCH_MIME_SUB) {
+		/* sublist of conditions */
+		i_assert(arg->value.subargs != NULL);
+
+		arg->result = 1;
+		subarg = arg->value.subargs;
+		while (subarg != NULL) {
+			if (subarg->result == -1)
+				search_mime_arg_foreach(subarg, callback, context);
+
+			if (subarg->result == -1)
+				arg->result = -1;
+			else if (subarg->result == 0) {
+				/* didn't match */
+				arg->result = 0;
+				break;
+			}
+
+			subarg = subarg->next;
+		}
+		if (arg->match_not && arg->result != -1)
+			arg->result = arg->result > 0 ? 0 : 1;
+	} else if (arg->type == SEARCH_MIME_OR) {
+		/* OR-list of conditions */
+		i_assert(arg->value.subargs != NULL);
+
+		subarg = arg->value.subargs;
+		arg->result = 0;
+		while (subarg != NULL) {
+			if (subarg->result == -1)
+				search_mime_arg_foreach(subarg, callback, context);
+
+			if (subarg->result == -1)
+				arg->result = -1;
+			else if (subarg->result > 0) {
+				/* matched */
+				arg->result = 1;
+				break;
+			}
+
+			subarg = subarg->next;
+		}
+		if (arg->match_not && arg->result != -1)
+			arg->result = arg->result > 0 ? 0 : 1;
+	} else {
+		/* just a single condition */
+		callback(arg, context);
+	}
+}
+
+#undef mail_search_mime_args_foreach
+int mail_search_mime_args_foreach(struct mail_search_mime_arg *args,
+			     mail_search_mime_foreach_callback_t *callback,
+			     void *context)
+{
+	int result;
+
+	result = 1;
+	for (; args != NULL; args = args->next) {
+		search_mime_arg_foreach(args, callback, context);
+
+		if (args->result == 0) {
+			/* didn't match */
+			return 0;
+		}
+
+		if (args->result == -1)
+			result = -1;
+	}
+
+	return result;
+}
+
+/*
+ *
+ */
+
+bool mail_search_mime_arg_one_equals(const struct mail_search_mime_arg *arg1,
+				const struct mail_search_mime_arg *arg2)
+{
+	if (arg1->type != arg2->type ||
+	    arg1->match_not != arg2->match_not)
+		return FALSE;
+
+	switch (arg1->type) {
+	case SEARCH_MIME_OR:
+	case SEARCH_MIME_SUB:
+		return mail_search_mime_arg_equals(arg1->value.subargs,
+					      arg2->value.subargs);
+
+	case SEARCH_MIME_SIZE_EQUAL:
+	case SEARCH_MIME_SIZE_LARGER:
+	case SEARCH_MIME_SIZE_SMALLER:
+		return arg1->value.size == arg2->value.size;
+
+	case SEARCH_MIME_HEADER:
+	case SEARCH_MIME_DISPOSITION_PARAM:
+	case SEARCH_MIME_PARAM:
+		if (strcasecmp(arg1->field_name, arg2->field_name) != 0)
+			return FALSE;
+		/* fall through */
+	case SEARCH_MIME_DESCRIPTION:
+	case SEARCH_MIME_DISPOSITION_TYPE:
+	case SEARCH_MIME_ENCODING:
+	case SEARCH_MIME_ID:
+	case SEARCH_MIME_LANGUAGE:
+	case SEARCH_MIME_LOCATION:
+	case SEARCH_MIME_MD5:
+	case SEARCH_MIME_TYPE:
+	case SEARCH_MIME_SUBTYPE:
+	case SEARCH_MIME_BODY:
+	case SEARCH_MIME_TEXT:
+	case SEARCH_MIME_CC:
+	case SEARCH_MIME_BCC:
+	case SEARCH_MIME_FROM:
+	case SEARCH_MIME_IN_REPLY_TO:
+	case SEARCH_MIME_MESSAGE_ID:
+	case SEARCH_MIME_REPLY_TO:
+	case SEARCH_MIME_SENDER:
+	case SEARCH_MIME_SUBJECT:
+	case SEARCH_MIME_TO:
+	case SEARCH_MIME_FILENAME_IS:
+	case SEARCH_MIME_FILENAME_CONTAINS:
+	case SEARCH_MIME_FILENAME_BEGINS:
+	case SEARCH_MIME_FILENAME_ENDS:
+		/* don't bother doing case-insensitive comparison. we should support
+		   full i18n case-insensitivity (or the active comparator
+		   in future). */
+		return strcmp(arg1->value.str, arg2->value.str) == 0;
+
+	case SEARCH_MIME_SENTBEFORE:
+	case SEARCH_MIME_SENTON:
+	case SEARCH_MIME_SENTSINCE:
+		return arg1->value.time == arg2->value.time;
+
+	case SEARCH_MIME_PARENT:
+	case SEARCH_MIME_CHILD:
+		if (arg1->value.subargs == NULL)
+			return arg2->value.subargs == NULL;
+		if (arg2->value.subargs == NULL)
+			return FALSE;
+		return mail_search_mime_arg_equals(arg1->value.subargs,
+					      arg2->value.subargs);
+
+	case SEARCH_MIME_DEPTH_EQUAL:
+	case SEARCH_MIME_DEPTH_MIN:
+	case SEARCH_MIME_DEPTH_MAX:
+	case SEARCH_MIME_INDEX:
+		return arg1->value.number == arg2->value.number;
+		break;
+	}
+	i_unreached();
+	return FALSE;
+}
+
+bool mail_search_mime_arg_equals(const struct mail_search_mime_arg *arg1,
+			    const struct mail_search_mime_arg *arg2)
+{
+	while (arg1 != NULL && arg2 != NULL) {
+		if (!mail_search_mime_arg_one_equals(arg1, arg2))
+			return FALSE;
+		arg1 = arg1->next;
+		arg2 = arg2->next;
+	}
+	return arg1 == NULL && arg2 == NULL;
+}
+
+bool mail_search_mime_parts_equal(const struct mail_search_mime_part *mpart1,
+			    const struct mail_search_mime_part *mpart2)
+{
+	i_assert(mpart1->simplified == mpart2->simplified);
+
+	return mail_search_mime_arg_equals(mpart1->args, mpart2->args);
+}
+
+/*
+ *
+ */
+
+void mail_search_mime_simplify(struct mail_search_mime_part *mpart)
+{
+	mpart->simplified = TRUE;
+
+	// FIXME: implement and use
+}
+
+/*
+ *
+ */
+
+static bool
+mail_search_mime_subargs_to_imap(string_t *dest,
+	const struct mail_search_mime_arg *args,
+	const char *prefix, const char **error_r)
+{
+	const struct mail_search_mime_arg *arg;
+
+	str_append_c(dest, '(');
+	for (arg = args; arg != NULL; arg = arg->next) {
+		if (arg->next != NULL)
+			str_append(dest, prefix);
+		if (!mail_search_mime_arg_to_imap(dest, arg, error_r))
+			return FALSE;
+		if (arg->next != NULL)
+			str_append_c(dest, ' ');
+	}
+	str_append_c(dest, ')');
+	return TRUE;
+}
+
+static bool
+mail_search_mime_arg_to_imap_date(string_t *dest,
+	const struct mail_search_mime_arg *arg)
+{
+	time_t timestamp = arg->value.time;
+	const char *str;
+	struct tm *tm;
+	int tz_offset;
+
+	tm = localtime(&timestamp);
+	tz_offset = utc_offset(tm, timestamp);
+	timestamp -= tz_offset * 60;
+
+	if (!imap_to_date(timestamp, &str))
+		return FALSE;
+	str_printfa(dest, " \"%s\"", str);
+	return TRUE;
+}
+
+bool mail_search_mime_arg_to_imap(string_t *dest,
+	const struct mail_search_mime_arg *arg, const char **error_r)
+{
+	if (arg->match_not)
+		str_append(dest, "NOT ");
+	switch (arg->type) {
+	case SEARCH_MIME_OR:
+		if (!mail_search_mime_subargs_to_imap
+			(dest, arg->value.subargs, "OR ", error_r))
+			return FALSE;
+		break;
+	case SEARCH_MIME_SUB:
+		if (!mail_search_mime_subargs_to_imap
+			(dest, arg->value.subargs, "", error_r))
+			return FALSE;
+		break;
+	case SEARCH_MIME_SIZE_EQUAL:
+		str_printfa(dest, "SIZE %llu",
+			(unsigned long long)arg->value.size);
+		break;
+	case SEARCH_MIME_SIZE_LARGER:
+		str_printfa(dest, "SIZE LARGER %llu",
+			(unsigned long long)arg->value.size);
+		break;
+	case SEARCH_MIME_SIZE_SMALLER:
+		str_printfa(dest, "SIZE SMALLER %llu",
+			(unsigned long long)arg->value.size);
+		break;
+	case SEARCH_MIME_DESCRIPTION:
+		str_append(dest, "DESCRIPTION ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_DISPOSITION_TYPE:
+		str_append(dest, "DISPOSITION TYPE ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_DISPOSITION_PARAM:
+		str_append(dest, "DISPOSITION PARAM ");
+		imap_append_astring(dest, arg->field_name);
+		str_append_c(dest, ' ');
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_ENCODING:
+		str_append(dest, "ENCODING ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_ID:
+		str_append(dest, "ID ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_LANGUAGE:
+		str_append(dest, "LANGUAGE ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_LOCATION:
+		str_append(dest, "LOCATION ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_MD5:
+		str_append(dest, "MD5 ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_TYPE:
+		str_append(dest, "TYPE ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_SUBTYPE:
+		str_append(dest, "SUBTYPE ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_PARAM:
+		str_append(dest, "PARAM ");
+		imap_append_astring(dest, arg->field_name);
+		str_append_c(dest, ' ');
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_HEADER:
+		str_append(dest, "HEADER ");
+		imap_append_astring(dest, arg->field_name);
+		str_append_c(dest, ' ');
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_BODY:
+		str_append(dest, "BODY ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_TEXT:
+		str_append(dest, "TEXT ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_CC:
+		str_append(dest, "CC ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_BCC:
+		str_append(dest, "BCC ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_FROM:
+		str_append(dest, "FROM ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_IN_REPLY_TO:
+		str_append(dest, "IN-REPLY-TO ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_MESSAGE_ID:
+		str_append(dest, "MESSAGE-ID ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_REPLY_TO:
+		str_append(dest, "REPLY-TO ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_SENDER:
+		str_append(dest, "SENDER ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_SENTBEFORE:
+		str_append(dest, "SENTBEFORE");
+		if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+			*error_r = t_strdup_printf(
+				"SENTBEFORE can't be written as IMAP MIMEPART key "
+				"for timestamp %ld", (long)arg->value.time);
+			return FALSE;
+		}
+		break;
+	case SEARCH_MIME_SENTON:
+		str_append(dest, "SENTON");
+		if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+			*error_r = t_strdup_printf(
+				"SENTON can't be written as IMAP MIMEPART key "
+				"for timestamp %ld", (long)arg->value.time);
+			return FALSE;
+		}
+		break;
+	case SEARCH_MIME_SENTSINCE:
+		str_append(dest, "SENTSINCE");
+		if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+			*error_r = t_strdup_printf(
+				"SENTSINCE can't be written as IMAP MIMEPART key "
+				"for timestamp %ld", (long)arg->value.time);
+			return FALSE;
+		}
+		break;
+	case SEARCH_MIME_SUBJECT:
+		str_append(dest, "SUBJECT ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_TO:
+		str_append(dest, "TO ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_DEPTH_EQUAL:
+		str_printfa(dest, "DEPTH %u", arg->value.number);
+		break;
+	case SEARCH_MIME_DEPTH_MIN:
+		str_printfa(dest, "DEPTH MIN %u", arg->value.number);
+		break;
+	case SEARCH_MIME_DEPTH_MAX:
+		str_printfa(dest, "DEPTH MAX %u", arg->value.number);
+		break;
+	case SEARCH_MIME_INDEX:
+		str_printfa(dest, "INDEX %u", arg->value.number);
+		break;
+	case SEARCH_MIME_PARENT:
+		str_append(dest, "PARENT ");
+		if (arg->value.subargs == NULL)
+			str_append(dest, "EXISTS");
+		else if (!mail_search_mime_subargs_to_imap
+			(dest, arg->value.subargs, "", error_r))
+			return FALSE;
+		break;
+	case SEARCH_MIME_CHILD:
+		str_append(dest, "CHILD ");
+		if (arg->value.subargs == NULL)
+			str_append(dest, "EXISTS");
+		else if (!mail_search_mime_subargs_to_imap
+			(dest, arg->value.subargs, "", error_r))
+			return FALSE;
+		break;
+	case SEARCH_MIME_FILENAME_IS:
+		str_append(dest, "FILENAME IS ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_FILENAME_CONTAINS:
+		str_append(dest, "FILENAME CONTAINS ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_FILENAME_BEGINS:
+		str_append(dest, "FILENAME BEGINS ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	case SEARCH_MIME_FILENAME_ENDS:
+		str_append(dest, "FILENAME ENDS ");
+		imap_append_astring(dest, arg->value.str);
+		break;
+	}
+	return TRUE;
+}
+
+bool mail_search_mime_part_to_imap(string_t *dest,
+	const struct mail_search_mime_part *mpart, const char **error_r)
+{
+	const struct mail_search_mime_arg *arg;
+
+	i_assert(mpart->args != NULL);
+	if (mpart->args->next == NULL) {
+		if (!mail_search_mime_arg_to_imap(dest, mpart->args, error_r))
+			return FALSE;
+	} else {
+		str_append_c(dest, '(');
+		for (arg = mpart->args; arg != NULL; arg = arg->next) {
+			if (!mail_search_mime_arg_to_imap(dest, arg, error_r))
+				return FALSE;
+			if (arg->next != NULL)
+				str_append_c(dest, ' ');
+		}
+		str_append_c(dest, ')');
+	}
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search-mime.h	Wed May 25 01:57:08 2016 +0200
@@ -0,0 +1,146 @@
+#ifndef MAIL_SEARCH_MIMEPART_H
+#define MAIL_SEARCH_MIMEPART_H
+
+enum mail_search_mime_arg_type {
+	SEARCH_MIME_OR,
+	SEARCH_MIME_SUB,
+
+	/* sizes */
+	SEARCH_MIME_SIZE_EQUAL,
+	SEARCH_MIME_SIZE_LARGER,
+	SEARCH_MIME_SIZE_SMALLER,
+
+	/* part properties */
+	SEARCH_MIME_DESCRIPTION,
+	SEARCH_MIME_DISPOSITION_TYPE,
+	SEARCH_MIME_DISPOSITION_PARAM,
+	SEARCH_MIME_ENCODING,
+	SEARCH_MIME_ID,
+	SEARCH_MIME_LANGUAGE,
+	SEARCH_MIME_LOCATION,
+	SEARCH_MIME_MD5,
+
+	/* content-type */
+	SEARCH_MIME_TYPE,
+	SEARCH_MIME_SUBTYPE,
+	SEARCH_MIME_PARAM,
+
+	/* headers */
+	SEARCH_MIME_HEADER,
+
+	/* body */
+	SEARCH_MIME_BODY,
+	SEARCH_MIME_TEXT,
+
+	/* message */
+	SEARCH_MIME_CC,
+	SEARCH_MIME_BCC,
+	SEARCH_MIME_FROM,
+	SEARCH_MIME_IN_REPLY_TO,
+	SEARCH_MIME_MESSAGE_ID,
+	SEARCH_MIME_REPLY_TO,
+	SEARCH_MIME_SENDER,
+	SEARCH_MIME_SENTBEFORE,
+	SEARCH_MIME_SENTON, /* time must point to beginning of the day */
+	SEARCH_MIME_SENTSINCE,
+	SEARCH_MIME_SUBJECT,
+	SEARCH_MIME_TO,
+
+	/* relations */
+	SEARCH_MIME_PARENT,
+	SEARCH_MIME_CHILD,
+
+	/* position */
+	SEARCH_MIME_DEPTH_EQUAL,
+	SEARCH_MIME_DEPTH_MIN,
+	SEARCH_MIME_DEPTH_MAX,
+	SEARCH_MIME_INDEX,
+
+	/* filename */
+	SEARCH_MIME_FILENAME_IS,
+	SEARCH_MIME_FILENAME_CONTAINS,
+	SEARCH_MIME_FILENAME_BEGINS,
+	SEARCH_MIME_FILENAME_ENDS
+};
+
+struct mail_search_mime_arg {
+	/* NOTE: when adding new fields, make sure mail_search_mime_arg_dup_one()
+	   and mail_search_mime_arg_one_equals() are updated. */
+	struct mail_search_mime_arg *next;
+
+	enum mail_search_mime_arg_type type;
+	union {
+		struct mail_search_mime_arg *subargs;
+		const char *str;
+		time_t time;
+		uoff_t size;
+		unsigned int number;
+	} value;
+
+	void *context;
+	const char *field_name; /* for SEARCH_HEADER* */
+	bool match_not:1; /* result = !result */
+	bool match_always:1; /* result = 1 always */
+	bool nonmatch_always:1; /* result = 0 always */
+
+	int result; /* -1 = unknown, 0 = unmatched, 1 = matched */
+};
+
+struct mail_search_mime_part {
+	struct mail_search_mime_arg *args;
+
+	bool simplified:1;
+};
+
+typedef void
+mail_search_mime_foreach_callback_t(struct mail_search_mime_arg *arg,
+					    void *context);
+
+/* Returns TRUE if the two mimepart search keys are fully compatible. */
+bool mail_search_mime_parts_equal(const struct mail_search_mime_part *mpart1,
+			    const struct mail_search_mime_part *mpart2);
+/* Same as mail_search_mime_part_equal(), but for individual
+   mail_search_mime_arg structs. All the siblings of arg1 and arg2 are
+   also compared. */
+bool mail_search_mime_arg_equals(const struct mail_search_mime_arg *arg1,
+			    const struct mail_search_mime_arg *arg2);
+/* Same as mail_search_mime_arg_equals(), but don't compare siblings. */
+bool mail_search_mime_arg_one_equals(const struct mail_search_mime_arg *arg1,
+				const struct mail_search_mime_arg *arg2);
+
+struct mail_search_mime_part *
+mail_search_mime_part_dup(pool_t pool,
+	const struct mail_search_mime_part *mpart);
+struct mail_search_mime_arg *
+mail_search_mime_arg_dup(pool_t pool,
+	const struct mail_search_mime_arg *arg);
+
+/* Reset the results in search arguments. match_always is reset only if
+   full_reset is TRUE. */
+void mail_search_mime_args_reset(struct mail_search_mime_arg *args,
+	bool full_reset);
+
+/* goes through arguments in list that don't have a result yet.
+   Returns 1 = search matched, 0 = search unmatched, -1 = don't know yet */
+int mail_search_mime_args_foreach(struct mail_search_mime_arg *args,
+			     mail_search_mime_foreach_callback_t *callback,
+			     void *context) ATTR_NULL(3);
+#define mail_search_mime_args_foreach(args, callback, context) \
+	  mail_search_mime_args_foreach(args + \
+		CALLBACK_TYPECHECK(callback, void (*)( \
+			struct mail_search_mime_arg *, typeof(context))), \
+		(mail_search_mime_foreach_callback_t *)callback, context)
+
+/* Simplify/optimize search arguments. Afterwards all OR/SUB args are
+   guaranteed to have match_not=FALSE. */
+void mail_search_mime_simplify(struct mail_search_mime_part *args);
+
+/* Appends MIMEPART search key to the dest string and returns TRUE. */
+bool mail_search_mime_part_to_imap(string_t *dest,
+	const struct mail_search_mime_part *mpart, const char **error_r);
+/* Like mail_search_mime_part_to_imap(), but append only a single MIMEPART
+   key. */
+bool mail_search_mime_arg_to_imap(string_t *dest,
+	const struct mail_search_mime_arg *arg, const char **error_r);
+
+#endif
--- a/src/lib-storage/mail-search-register-imap.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-search-register-imap.c	Wed May 25 01:57:08 2016 +0200
@@ -11,7 +11,7 @@
 #include "mail-search-register.h"
 #include "mail-search-parser.h"
 #include "mail-search-build.h"
-
+#include "mail-search-mime-build.h"
 
 struct mail_search_register *mail_search_register_imap;
 
@@ -451,6 +451,17 @@
 }
 
 static struct mail_search_arg *
+imap_search_mimepart(struct mail_search_build_context *ctx)
+{
+	struct mail_search_arg *sarg;
+
+	sarg = mail_search_build_new(ctx, SEARCH_MIMEPART);
+	if (mail_search_mime_build(ctx, &sarg->value.mime_part) < 0)
+		return NULL;
+	return sarg;
+}
+
+static struct mail_search_arg *
 imap_search_inthread(struct mail_search_build_context *ctx)
 {
 	struct mail_search_arg *sarg;
@@ -581,6 +592,9 @@
 	/* FUZZY extension: */
 	{ "FUZZY", imap_search_fuzzy },
 
+	/* SEARCH=MIMEPART extension: */
+	{ "MIMEPART", imap_search_mimepart },
+
 	/* Other Dovecot extensions: */
 	{ "INTHREAD", imap_search_inthread },
 	{ "X-GUID", imap_search_x_guid },
--- a/src/lib-storage/mail-search.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-search.c	Wed May 25 01:57:08 2016 +0200
@@ -8,6 +8,7 @@
 #include "mail-namespace.h"
 #include "mail-search-build.h"
 #include "mail-search.h"
+#include "mail-search-mime.h"
 
 static void
 mailbox_uidset_change(struct mail_search_arg *arg, struct mailbox *box,
@@ -314,6 +315,10 @@
 			p_new(pool, struct mail_search_modseq, 1);
 		*new_arg->value.modseq = *arg->value.modseq;
 		break;
+	case SEARCH_MIMEPART:
+		new_arg->value.mime_part =
+			mail_search_mime_part_dup(pool, arg->value.mime_part);
+		break;
 	}
 	return new_arg;
 }
@@ -652,6 +657,10 @@
 			return FALSE;
 		return mail_search_arg_equals(arg1->value.subargs,
 					      arg2->value.subargs);
+	case SEARCH_MIMEPART:
+		return mail_search_mime_parts_equal(arg1->value.mime_part,
+					      arg2->value.mime_part);
+
 	}
 	i_unreached();
 	return FALSE;
--- a/src/lib-storage/mail-search.h	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-search.h	Wed May 25 01:57:08 2016 +0200
@@ -5,6 +5,8 @@
 #include "mail-types.h"
 #include "mail-thread.h"
 
+struct mail_search_mime_part;
+
 enum mail_search_arg_type {
 	SEARCH_OR,
 	SEARCH_SUB,
@@ -43,7 +45,8 @@
 	SEARCH_MAILBOX,
 	SEARCH_MAILBOX_GUID,
 	SEARCH_MAILBOX_GLOB,
-	SEARCH_REAL_UID
+	SEARCH_REAL_UID,
+	SEARCH_MIMEPART
 };
 
 enum mail_search_date_type {
@@ -101,6 +104,7 @@
 		enum mail_thread_type thread_type;
 		struct mail_search_modseq *modseq;
 		struct mail_search_result *search_result;
+		struct mail_search_mime_part *mime_part;
 	} value;
 	/* set by mail_search_args_init(): */
 	struct {
--- a/src/lib-storage/mail-storage.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/mail-storage.c	Wed May 25 01:57:08 2016 +0200
@@ -24,6 +24,7 @@
 #include "mail-namespace.h"
 #include "mail-search.h"
 #include "mail-search-register.h"
+#include "mail-search-mime-register.h"
 #include "mailbox-search-result-private.h"
 #include "mailbox-guid-cache.h"
 #include "mail-cache.h"
@@ -67,6 +68,7 @@
 		mail_search_register_deinit(&mail_search_register_human);
 	if (mail_search_register_imap != NULL)
 		mail_search_register_deinit(&mail_search_register_imap);
+	mail_search_mime_register_deinit();
 	if (array_is_created(&mail_storage_classes))
 		array_free(&mail_storage_classes);
 	mail_storage_hooks_deinit();
--- a/src/lib-storage/test-mail-search-args-imap.c	Sat Jan 14 13:33:05 2017 +0100
+++ b/src/lib-storage/test-mail-search-args-imap.c	Wed May 25 01:57:08 2016 +0200
@@ -61,7 +61,50 @@
 	  "INTHREAD REFS (((OR TEXT foo OR KEYWORD bar (SEEN))))" },
 	{ "X-GUID foo", NULL },
 	{ "X-MAILBOX foo", NULL },
-	{ "X-REAL-UID 1,5:6,10:15", NULL }
+	{ "X-REAL-UID 1,5:6,10:15", NULL },
+	/* SEARCH=X-MIMEPART */
+	{ "MIMEPART CHILD EXISTS", NULL },
+	{ "MIMEPART ( CHILD EXISTS )",
+	  "MIMEPART CHILD EXISTS" },
+	{ "MIMEPART ( CHILD EXISTS HEADER Comment Hopla )",
+	  "MIMEPART (CHILD EXISTS HEADER COMMENT Hopla)" },
+	{ "MIMEPART ( DESCRIPTION Frop ENCODING base64 )",
+	  "MIMEPART (DESCRIPTION Frop ENCODING base64)" },
+	{ "MIMEPART ( DISPOSITION TYPE attachment "
+	    "DISPOSITION PARAM FILENAME frop.txt )",
+	  "MIMEPART (DISPOSITION TYPE attachment "
+	    "DISPOSITION PARAM FILENAME frop.txt)" },
+	{ "MIMEPART ( ID <frop.example.com> LANGUAGE en )",
+	  "MIMEPART (ID <frop.example.com> LANGUAGE en)" },
+	{ "MIMEPART ( LOCATION http://www.dovecot.org )",
+	  "MIMEPART LOCATION http://www.dovecot.org" },
+	{ "MIMEPART NOT MD5 373def35afde6378efd6172dfeadfd", NULL },
+	{ "MIMEPART OR PARAM charset utf-8 TYPE text",
+	  "MIMEPART (OR PARAM CHARSET utf-8 TYPE text)" },
+	{ "MIMEPART ( OR SIZE LARGER 25 SIZE SMALLER 1023 )",
+	  "MIMEPART (OR SIZE LARGER 25 SIZE SMALLER 1023)" },
+	{ "MIMEPART ( TYPE video SUBTYPE mpeg )",
+	  "MIMEPART (TYPE video SUBTYPE mpeg)" },
+	{ "( OR MIMEPART ( DEPTH 2 INDEX 1 ) MIMEPART ( DEPTH MAX 4 INDEX 3 ) )",
+	  "((OR MIMEPART (DEPTH 2 INDEX 1) MIMEPART (DEPTH MAX 4 INDEX 3)))" },
+	{ "MIMEPART FILENAME IS frop.txt", NULL },
+	{ "MIMEPART FILENAME BEGINS frop", NULL },
+	{ "MIMEPART FILENAME ENDS .txt", NULL },
+	{ "MIMEPART FILENAME CONTAINS frop", NULL },
+	{ "MIMEPART BODY frop MIMEPART TEXT frop", NULL },
+	{ "MIMEPART ( CC appie BCC theo FROM leo REPLY-TO henk SENDER arie )",
+	  "MIMEPART (CC appie BCC theo FROM leo REPLY-TO henk SENDER arie)" },
+	{ "MIMEPART ( MESSAGE-ID <frop4222> IN-REPLY-TO <frop421> )",
+	  "MIMEPART (MESSAGE-ID <frop4222> IN-REPLY-TO <frop421>)" },
+	{ "MIMEPART ( SUBJECT Frop TO henkie SENTON 20-Feb-2017 )",
+	  "MIMEPART (SUBJECT Frop TO henkie SENTON \"20-Feb-2017\")" },
+	{ "MIMEPART ( OR SENTBEFORE 20-May-2015 SENTSINCE 20-Feb-2017 )",
+	  "MIMEPART (OR SENTBEFORE \"20-May-2015\" SENTSINCE \"20-Feb-2017\")" },
+	{ "MIMEPART ( ID <frop> PARENT ID <friep> )",
+	  "MIMEPART (ID <frop> PARENT (ID <friep>))" },
+	{ "MIMEPART ( ID <frop> CHILD ( DESCRIPTION frop ID friep ) )",
+	  "MIMEPART (ID <frop> CHILD (DESCRIPTION frop ID friep))" },
+	{ "MIMEPART CHILD EXISTS MIMEPART PARENT EXISTS", NULL },
 };
 
 static struct mail_search_arg test_failures[] = {