diff --git a/Makefile.am b/Makefile.am
index efca13914f969f95071e7cc963daa570a1aceda0..6fe58623a8f9c073cc0c1707327eec120b43d321 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -166,6 +166,8 @@ test_cases = \
 	tests/extensions/duplicate/execute-vnd.svtest \
 	tests/extensions/metadata/execute.svtest \
 	tests/extensions/metadata/errors.svtest \
+	tests/extensions/mime/errors.svtest \
+	tests/extensions/mime/content-header.svtest \
 	tests/extensions/vnd.dovecot/debug/execute.svtest \
 	tests/extensions/vnd.dovecot/environment/basic.svtest \
 	tests/extensions/vnd.dovecot/environment/variables.svtest \
diff --git a/README b/README
index 4ea77be22c8db83715169f56fe480d27ef8b2a48..ca04fae8bfbafc824f0209005b42ccafd8a91b78 100644
--- a/README
+++ b/README
@@ -120,6 +120,8 @@ following list outlines the implementation status of each supported extension:
     mailbox (RFC 5490; Section 3): fully supported (v0.1.10+), but ACL
         permissions are not verified for mailboxexists.
     mboxmetadata and servermetadata (RFC 5490): fully supported (v0.4.7+)
+		foreverypart (RFC 5703; Section 3): fully supported (v0.4.10+).
+		mime (RFC 5703; Section 4): fully supported (v0.4.10+). 
     include (RFC 6609): fully supported (v0.4.0+)
     duplicate (RFC 7352): fully supported (v0.4.3+).
     regex (draft v08; not latest version): almost fully supported, but
@@ -154,7 +156,7 @@ following list outlines the implementation status of each supported extension:
   useful for Dovecot in particular, but many of them are. Currently, the
   author has taken notice of the following extensions:
 
-    foreverypart, mime, replace, enclose, and extracttext (RFC 5703): planned.
+    replace, enclose, and extracttext (RFC 5703): planned.
     imapsieve (RFC 6785): planned.
     envelope-dsn, envelope-deliverby, redirect-dsn and
       redirect-deliverby (RFC 6009): planned; depends on lib-smtp changes in
diff --git a/TODO b/TODO
index 82675b7f5907b2d1c2e9fe1790e12df952bea53c..285914545ba313c24c1357d3e72ae8aa35b12f55 100644
--- a/TODO
+++ b/TODO
@@ -33,7 +33,7 @@ Next (mostly in order of descending priority/precedence):
 	- Adjust Sieve script API to support asynchronous script retrieval to
 	  retrieve scripts in parallel when possible.
 * Implement message modification and extraction API in order to:
-	- Implement replace, enclose, foreverypart, mime and extracttext extensions
+	- Implement replace, enclose, and extracttext extensions
 * Improve error handling.
 	- Implement dropping errors in the user's mailbox as a mail message.
 * Finish body extension:
diff --git a/configure.ac b/configure.ac
index c64f3dabe8a7be2928e2fc8a6551cc3bfa66301a..1fd885d8b5f3c6aed4a2fd2099d6d8bc9ae2cfd7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -209,6 +209,7 @@ src/lib-sieve/plugins/editheader/Makefile
 src/lib-sieve/plugins/metadata/Makefile
 src/lib-sieve/plugins/duplicate/Makefile
 src/lib-sieve/plugins/index/Makefile
+src/lib-sieve/plugins/mime/Makefile
 src/lib-sieve/plugins/vnd.dovecot/Makefile
 src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
 src/lib-sieve/plugins/vnd.dovecot/environment/Makefile
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 98ae11f178897b31dc4a646659179a3ae7796564..99ca22bb2c59b2f9f3a807a6a3efff4165448c05 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -77,6 +77,7 @@ plugins = \
 	$(extdir)/duplicate/libsieve_ext_duplicate.la \
 	$(extdir)/index/libsieve_ext_index.la \
 	$(extdir)/metadata/libsieve_ext_metadata.la \
+	$(extdir)/mime/libsieve_ext_mime.la \
 	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
 	$(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
 	$(unfinished_plugins)
diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am
index 4e626582d30b27bd42fc041272b411e706bf3e0a..887235f1c1508003ca670403fc9b1e7a6cbf79fa 100644
--- a/src/lib-sieve/plugins/Makefile.am
+++ b/src/lib-sieve/plugins/Makefile.am
@@ -24,6 +24,7 @@ SUBDIRS = \
 	duplicate \
 	index \
 	metadata \
+	mime \
 	vnd.dovecot \
 	$(UNFINISHED)
 
diff --git a/src/lib-sieve/plugins/mime/Makefile.am b/src/lib-sieve/plugins/mime/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..07901912b80c94d6aee2b78e3ff2fe3c55894060
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_mime.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-foreverypart.c \
+	cmd-break.c
+
+tags = \
+	tag-mime.c
+
+libsieve_ext_mime_la_SOURCES = \
+	ext-mime.c \
+	ext-foreverypart.c \
+	ext-mime-common.c \
+	$(commands) \
+	$(tags)
+
+noinst_HEADERS = \
+	ext-mime-common.h
+
diff --git a/src/lib-sieve/plugins/mime/cmd-break.c b/src/lib-sieve/plugins/mime/cmd-break.c
new file mode 100644
index 0000000000000000000000000000000000000000..a5204f6686df461bb945c1e1a7aa72974422e24a
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/cmd-break.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* break
+ *
+ * Syntax:
+ *   break [":name" <name: string>]
+ *
+ */
+
+static bool cmd_break_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_break_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_break = {
+	"break",
+	SCT_COMMAND,
+	0, 0, FALSE, FALSE,
+	cmd_break_registered,
+	cmd_break_pre_validate,
+	cmd_break_validate,
+	NULL,
+	cmd_break_generate,
+	NULL,
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_break_validate_name_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def break_name_tag = {
+	"name",
+	NULL,
+	cmd_break_validate_name_tag,
+	NULL, NULL, NULL
+};
+
+/*
+ * Break operation
+ */
+
+static bool cmd_break_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_break_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def break_operation = {
+	"break",
+	&foreverypart_extension,
+	EXT_FOREVERYPART_OPERATION_BREAK,
+	cmd_break_operation_dump,
+	cmd_break_operation_execute
+};
+
+/*
+ * Validation data
+ */
+
+struct cmd_break_data {
+	struct sieve_ast_argument *name;
+	struct sieve_command *loop_cmd;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_break_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :name <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+		return FALSE;
+	data->name = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_break_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &break_name_tag, 0);
+	
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_break_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data;
+	pool_t pool = sieve_command_pool(cmd);
+	
+	data = p_new(pool, struct cmd_break_data, 1);
+	cmd->data = data;
+	return TRUE;
+}
+
+static bool cmd_break_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct sieve_ast_node *node = cmd->ast_node;
+	const char *name =	( data->name == NULL ?
+		NULL : sieve_ast_argument_strc(data->name) );
+
+	i_assert(node != NULL);
+	while ( node != NULL && node->command != NULL ) {
+		if ( sieve_command_is(node->command, cmd_foreverypart) ) {
+			struct ext_foreverypart_loop *loop =
+				(struct ext_foreverypart_loop *)node->command->data;
+			if ( name == NULL ||
+				(name != NULL && loop->name != NULL &&
+					strcmp(name, loop->name) == 0) ) {
+				data->loop_cmd = node->command;
+				break;
+			}
+		}
+		node = sieve_ast_node_parent(node);
+	}
+
+	if ( data->loop_cmd == NULL ) {
+		if ( name == NULL ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the break command is not placed inside "
+				"a foreverypart loop");
+		} else {
+			sieve_command_validate_error(valdtr, cmd,
+				"the break command is not placed inside "
+				"a foreverypart loop named `%s'",
+				name);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_break_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct ext_foreverypart_loop *loop;
+
+	i_assert( data->loop_cmd != NULL );
+	loop = (struct ext_foreverypart_loop *)data->loop_cmd->data;
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &break_operation);
+	sieve_jumplist_add(loop->exit_jumps,
+		sieve_binary_emit_offset(cgenv->sblock, 0));
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_break_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "BREAK");
+	sieve_code_descend(denv);
+
+ 	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_break_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_end;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop end offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_end = pc + offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "break command");
+	sieve_runtime_trace_descend(renv);
+
+	loop = sieve_interpreter_loop_get
+		(renv->interp, loop_end, &foreverypart_extension);
+	if ( loop == NULL ) {
+		sieve_runtime_trace_error(renv, "no matching loop found");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	sieve_interpreter_loop_break(renv->interp, loop);
+	return SIEVE_EXEC_OK;
+}
+
+
diff --git a/src/lib-sieve/plugins/mime/cmd-foreverypart.c b/src/lib-sieve/plugins/mime/cmd-foreverypart.c
new file mode 100644
index 0000000000000000000000000000000000000000..c5d2ba6173d0e6e6d708c07bcf7f9b31750176b3
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/cmd-foreverypart.c
@@ -0,0 +1,339 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-message.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* Foreverypart
+ *
+ * Syntax:
+ *   foreverypart [":name" <name: string>] <block>
+ *
+ */
+
+static bool cmd_foreverypart_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_foreverypart_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_foreverypart_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_foreverypart = {
+	"foreverypart",
+	SCT_COMMAND,
+	0, 0, TRUE, TRUE,
+	cmd_foreverypart_registered,
+	cmd_foreverypart_pre_validate,
+	NULL, NULL,
+	cmd_foreverypart_generate,
+	NULL,
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_foreverypart_validate_name_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def foreverypart_name_tag = {
+	"name",
+	NULL,
+	cmd_foreverypart_validate_name_tag,
+	NULL, NULL, NULL
+};
+
+/*
+ * foreverypart operation
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_begin_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_begin_operation = {
+	"FOREVERYPART_BEGIN",
+	&foreverypart_extension,
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+	cmd_foreverypart_begin_operation_dump,
+	cmd_foreverypart_begin_operation_execute
+};
+
+static bool cmd_foreverypart_end_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_end_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_end_operation = {
+	"FOREVERYPART_END",
+	&foreverypart_extension,
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+	cmd_foreverypart_end_operation_dump,
+	cmd_foreverypart_end_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_foreverypart_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop =
+		(struct ext_foreverypart_loop *)cmd->data;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :name <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+		return FALSE;
+	loop->name = sieve_ast_argument_strc(*arg);
+
+	/* Detach parameter */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_foreverypart_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &foreverypart_name_tag, 0);
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_foreverypart_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop;
+	pool_t pool = sieve_command_pool(cmd);
+	
+	loop = p_new(pool, struct ext_foreverypart_loop, 1);
+	cmd->data = loop;		
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_foreverypart_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop =
+		(struct ext_foreverypart_loop *)cmd->data;
+	sieve_size_t block_begin, loop_jump;
+
+	/* Emit FOREVERYPART_BEGIN operation */
+	sieve_operation_emit(cgenv->sblock,
+		cmd->ext, &foreverypart_begin_operation);
+
+	/* Emit exit address */
+	loop->exit_jumps = sieve_jumplist_create
+		(sieve_command_pool(cmd), cgenv->sblock);
+	sieve_jumplist_add(loop->exit_jumps,
+		sieve_binary_emit_offset(cgenv->sblock, 0));
+	block_begin = sieve_binary_block_get_size(cgenv->sblock);
+
+	/* Generate loop block */
+	if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+		return FALSE;
+
+	/* Emit FOREVERYPART_END operation */
+	sieve_operation_emit(cgenv->sblock,
+		cmd->ext, &foreverypart_end_operation);
+	loop_jump = sieve_binary_block_get_size(cgenv->sblock);
+	i_assert(loop_jump > block_begin);
+	(void)sieve_binary_emit_offset
+		(cgenv->sblock, (loop_jump - block_begin));
+	
+	/* Resolve exit address */
+	sieve_jumplist_resolve(loop->exit_jumps);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "FOREVERYPART_BEGIN");
+	sieve_code_descend(denv);
+
+ 	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+	return TRUE;
+}
+
+static bool cmd_foreverypart_end_operation_dump
+(const struct sieve_dumptime_env *denv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "FOREVERYPART_END");
+	sieve_code_descend(denv);
+
+	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "BEGIN: -%d [%08x]", offset, pc - offset);
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_foreverypart_begin_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop, *sfploop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_end;
+	pool_t pool;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop end offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_end = pc + offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "foreverypart loop begin");
+	sieve_runtime_trace_descend(renv);
+
+	sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+
+	loop = sieve_interpreter_loop_start
+		(renv->interp, loop_end, &foreverypart_extension);
+	if ( loop == NULL )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	pool = sieve_interpreter_loop_get_pool(loop);
+	fploop = p_new(pool, struct ext_foreverypart_runtime_loop, 1);
+	
+	if ( sfploop == NULL ) {
+		if ( (ret=sieve_message_part_iter_init
+			(&fploop->part_iter, renv)) <= 0 )
+			return ret;
+	} else {
+		sieve_message_part_iter_children(&sfploop->part_iter,
+			&fploop->part_iter);
+	}
+	fploop->part = sieve_message_part_iter_current(&fploop->part_iter);
+	if (fploop->part != NULL) {
+		sieve_interpreter_loop_set_context(loop, (void*)fploop);
+	} else {
+		/* No children parts to iterate */
+		sieve_interpreter_loop_break(renv->interp, loop);
+	} 
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_foreverypart_end_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_begin;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop begin offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_begin = pc - offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv,
+		SIEVE_TRLVL_ACTIONS, "foreverypart loop end");
+	sieve_runtime_trace_descend(renv);
+
+	loop = sieve_interpreter_loop_get
+		(renv->interp, *address, &foreverypart_extension);
+	if ( loop == NULL ) {
+		sieve_runtime_trace_error(renv, "no matching loop found");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	fploop = (struct ext_foreverypart_runtime_loop *)
+		sieve_interpreter_loop_get_context(loop);
+	i_assert(fploop->part != NULL);
+	fploop->part = sieve_message_part_iter_next(&fploop->part_iter);
+	if ( fploop->part == NULL )
+		sieve_interpreter_loop_break(renv->interp, loop);
+	else
+		sieve_interpreter_loop_next(renv->interp, loop, loop_begin);
+	
+	return SIEVE_EXEC_OK;
+}
+
+
diff --git a/src/lib-sieve/plugins/mime/ext-foreverypart.c b/src/lib-sieve/plugins/mime/ext-foreverypart.c
new file mode 100644
index 0000000000000000000000000000000000000000..c0e22388025f5edfc89ea3d63dcbf9036efb2e06
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/ext-foreverypart.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension foreverypart
+ * ----------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703,Section 3
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_foreverypart_operations[] = {
+	&foreverypart_begin_operation,
+	&foreverypart_end_operation,
+	&break_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_foreverypart_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def foreverypart_extension = {
+	.name = "foreverypart",
+	.validator_load = ext_foreverypart_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_foreverypart_operations)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_foreverypart_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_foreverypart);
+	sieve_validator_register_command(valdtr, ext, &cmd_break);
+
+	return TRUE;
+}
diff --git a/src/lib-sieve/plugins/mime/ext-mime-common.c b/src/lib-sieve/plugins/mime/ext-mime-common.c
new file mode 100644
index 0000000000000000000000000000000000000000..21d49048aa11cd669d062c287cc41433c6efdb96
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/ext-mime-common.c
@@ -0,0 +1,27 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-interpreter.h"
+
+#include "ext-mime-common.h"
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop;
+
+	loop = sieve_interpreter_loop_get_surrounding
+		(renv->interp, NULL, &foreverypart_extension);
+	if ( loop == NULL ) {
+		fploop = NULL;
+	} else {
+		fploop = (struct ext_foreverypart_runtime_loop *)
+			sieve_interpreter_loop_get_context(loop);
+		i_assert(fploop->part != NULL);
+	}
+
+	return fploop;
+}
diff --git a/src/lib-sieve/plugins/mime/ext-mime-common.h b/src/lib-sieve/plugins/mime/ext-mime-common.h
new file mode 100644
index 0000000000000000000000000000000000000000..9607bfa933c02052c98ebe356c185afbaf58b16a
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/ext-mime-common.h
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#ifndef __EXT_FOREVERYPART_COMMON_H
+#define __EXT_FOREVERYPART_COMMON_H
+
+#include "sieve-message.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def foreverypart_extension;
+extern const struct sieve_extension_def mime_extension;
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def mime_tag;
+extern const struct sieve_argument_def mime_anychild_tag;
+extern const struct sieve_argument_def mime_type_tag;
+extern const struct sieve_argument_def mime_subtype_tag;
+extern const struct sieve_argument_def mime_contenttype_tag;
+extern const struct sieve_argument_def mime_param_tag;
+
+/*
+ * Commands
+ */
+
+struct ext_foreverypart_loop {
+	const char *name;
+	struct sieve_jumplist *exit_jumps;
+};
+
+extern const struct sieve_command_def cmd_foreverypart;
+extern const struct sieve_command_def cmd_break;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def foreverypart_begin_operation;
+extern const struct sieve_operation_def foreverypart_end_operation;
+extern const struct sieve_operation_def break_operation;
+
+enum ext_foreverypart_opcode {
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+	EXT_FOREVERYPART_OPERATION_BREAK,
+};
+
+/*
+ * Operands
+ */
+
+enum ext_mime_option {
+	EXT_MIME_OPTION_NONE = 0,
+	EXT_MIME_OPTION_TYPE,
+	EXT_MIME_OPTION_SUBTYPE,
+	EXT_MIME_OPTION_CONTENTTYPE,
+	EXT_MIME_OPTION_PARAM
+};
+
+extern const struct sieve_operand_def mime_operand;
+
+/*
+ * Foreverypart loop
+ */
+
+struct ext_foreverypart_runtime_loop {
+	struct sieve_message_part_iter part_iter;
+	struct sieve_message_part *part;
+};
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv);
+
+#endif /* __EXT_FOREVERYPART_COMMON_H */
diff --git a/src/lib-sieve/plugins/mime/ext-mime.c b/src/lib-sieve/plugins/mime/ext-mime.c
new file mode 100644
index 0000000000000000000000000000000000000000..5f8e786acc9b03db660201a20b72dec064dbcfc1
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/ext-mime.c
@@ -0,0 +1,77 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension mime
+ * --------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703,Section 4
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_mime_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mime_extension = {
+	.name = "mime",
+	.validator_load = ext_mime_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(mime_operand)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_mime_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register :mime tag and friends with header, address and exists test
+	 * commands and we don't care whether these command are registered or
+	 * even whether these will be registered at all. The validator handles
+	 * either situation gracefully.
+	 */
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_anychild_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_type_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_subtype_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_contenttype_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_param_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &mime_anychild_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "exists", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "exists", ext, &mime_anychild_tag, 0);
+
+	return TRUE;
+}
diff --git a/src/lib-sieve/plugins/mime/tag-mime.c b/src/lib-sieve/plugins/mime/tag-mime.c
new file mode 100644
index 0000000000000000000000000000000000000000..28ee54572852c6906b2df9492959960eb777cadd
--- /dev/null
+++ b/src/lib-sieve/plugins/mime/tag-mime.c
@@ -0,0 +1,704 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_mime_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_mime_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *context);
+
+const struct sieve_argument_def mime_tag = {
+	"mime",
+	NULL,
+	tag_mime_validate,
+	NULL, NULL,
+	tag_mime_generate
+};
+
+static bool tag_mime_option_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def mime_anychild_tag = {
+	"anychild",
+	NULL,
+	tag_mime_option_validate,
+	NULL, NULL, NULL
+};
+
+const struct sieve_argument_def mime_type_tag = {
+	"type",
+	NULL,
+	tag_mime_option_validate,
+	NULL, NULL, NULL
+};
+
+const struct sieve_argument_def mime_subtype_tag = {
+	"subtype",
+	NULL,
+	tag_mime_option_validate,
+	NULL, NULL, NULL
+};
+
+const struct sieve_argument_def mime_contenttype_tag = {
+	"contenttype",
+	NULL,
+	tag_mime_option_validate,
+	NULL, NULL, NULL
+};
+
+const struct sieve_argument_def mime_param_tag = {
+	"param",
+	NULL,
+	tag_mime_option_validate,
+	NULL, NULL, NULL
+};
+
+/*
+ * Header override
+ */
+
+static bool svmo_mime_dump_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int svmo_mime_read_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv, sieve_size_t *address,
+		void **ho_context);
+static int svmo_mime_header_override
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv,
+		bool mime_decode, struct sieve_stringlist **headers);
+
+const struct sieve_message_override_def mime_header_override = {
+	SIEVE_OBJECT("mime", &mime_operand, 0),
+	.sequence = 0, /* Completely replace header source */
+	.dump_context = svmo_mime_dump_context,
+	.read_context = svmo_mime_read_context,
+	.header_override = svmo_mime_header_override
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_header_overrides =
+	SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(mime_header_override);
+
+const struct sieve_operand_def mime_operand = {
+	"mime operand",
+	&mime_extension,
+	0,
+	&sieve_message_override_operand_class,
+	&ext_header_overrides
+};
+
+/*
+ * Tag data
+ */
+
+struct tag_mime_data {
+	enum ext_mime_option mimeopt;
+	struct sieve_ast_argument *param_arg;
+	unsigned int anychild:1;
+};
+
+/*
+ * Tag validation
+ */
+
+static struct tag_mime_data *
+tag_mime_get_data(struct sieve_command *cmd,
+	struct sieve_ast_argument *tag)
+{
+	struct tag_mime_data *data;
+
+	if (tag->argument->data == NULL) {
+		data = p_new(sieve_command_pool(cmd), struct tag_mime_data, 1);
+		tag->argument->data = (void *)data;
+	} else {
+		data = (struct tag_mime_data *)tag->argument->data;
+	}
+
+	return data;
+}
+
+static bool tag_mime_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Skip the tag itself */
+	*arg = sieve_ast_argument_next(*arg);
+
+	(void)tag_mime_get_data(cmd, tag);
+	return TRUE;
+}
+
+static bool tag_mime_option_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct sieve_ast_argument *mime_arg;
+	struct tag_mime_data *data;
+
+	/* Detach tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Find required ":mime" tag */
+	mime_arg = sieve_command_find_argument(cmd, &mime_tag);
+	if ( mime_arg == NULL ) {
+		sieve_argument_validate_error(valdtr, tag,
+			"the :%s tag for the %s %s cannot be specified "
+			"without the :mime tag", sieve_ast_argument_tag(tag),
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* Annotate ":mime" tag with the data provided by this option tag */
+	data = tag_mime_get_data(cmd, mime_arg);
+	if ( sieve_argument_is(tag, mime_anychild_tag) )
+		data->anychild = TRUE;
+	else 	{
+		if ( data->mimeopt != EXT_MIME_OPTION_NONE ) {
+			sieve_argument_validate_error(valdtr, *arg,
+				"the :type, :subtype, :contenttype, and :param "
+				"arguments for the %s test are mutually exclusive, "
+				"but more than one was specified",
+				sieve_command_identifier(cmd));
+			return FALSE;
+		}
+		if ( sieve_argument_is(tag, mime_type_tag) )
+			data->mimeopt = EXT_MIME_OPTION_TYPE;
+		else 	if ( sieve_argument_is(tag, mime_subtype_tag) )
+			data->mimeopt = EXT_MIME_OPTION_SUBTYPE;
+		else 	if ( sieve_argument_is(tag, mime_contenttype_tag) )
+			data->mimeopt = EXT_MIME_OPTION_CONTENTTYPE;
+		else 	if ( sieve_argument_is(tag, mime_param_tag) ) {
+			/* Check syntax:
+			 *   ":param" <param-list: string-list>
+			 */
+			if ( !sieve_validate_tag_parameter
+				(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+				return FALSE;
+			}
+
+			data->mimeopt = EXT_MIME_OPTION_PARAM;
+			data->param_arg = *arg;
+
+			/* Detach parameter */
+			*arg = sieve_ast_arguments_detach(*arg,1);
+		} else
+			i_unreached();
+	}
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_mime_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	struct tag_mime_data *data =
+		(struct tag_mime_data *)arg->argument->data;
+
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG )
+		return FALSE;
+
+	sieve_opr_message_override_emit
+		(cgenv->sblock, arg->argument->ext, &mime_header_override);
+
+	(void)sieve_binary_emit_byte
+		(cgenv->sblock, ( data->anychild ? 1 : 0 ));
+	(void)sieve_binary_emit_byte
+		(cgenv->sblock, data->mimeopt);
+	if ( data->mimeopt == EXT_MIME_OPTION_PARAM &&
+		!sieve_generate_argument(cgenv, data->param_arg, cmd) )
+		return FALSE;
+	return TRUE;
+}
+
+/*
+ * Content-type stringlist
+ */
+
+enum content_type_part {
+	CONTENT_TYPE_PART_NONE = 0,
+	CONTENT_TYPE_PART_TYPE,
+	CONTENT_TYPE_PART_SUBTYPE,
+	CONTENT_TYPE_PART_CONTENTTYPE,
+};
+
+/* Object */
+
+static int content_header_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void content_header_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int content_header_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+static void content_header_stringlist_set_trace
+	(struct sieve_stringlist *strlist, bool trace);
+
+struct content_header_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_header_list *source;
+
+	enum ext_mime_option option;
+	const char *const *params;
+
+	const char *const *param_values;
+};
+
+static struct sieve_stringlist *content_header_stringlist_create
+(const struct sieve_runtime_env *renv,
+	struct sieve_header_list *source,
+	enum ext_mime_option option, const char *const *params)
+{
+	struct content_header_stringlist *strlist;
+
+	strlist = t_new(struct content_header_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = content_header_stringlist_next_item;
+	strlist->strlist.reset = content_header_stringlist_reset;
+	strlist->strlist.set_trace = content_header_stringlist_set_trace;
+	strlist->source = source;
+	strlist->option = option;
+	strlist->params = params;
+
+	if ( option != EXT_MIME_OPTION_PARAM ) {
+		/* One header can have multiple parameters, so we cannot rely
+		   on the source length for the :param option. */
+		strlist->strlist.get_length = content_header_stringlist_get_length;
+	}
+
+	return &strlist->strlist;
+}
+
+/* Implementation */
+
+static inline int _decode_hex_digit(const unsigned char digit)
+{
+	switch ( digit ) {
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		return digit - '0';
+
+	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+		return digit - 'a' + 0x0a;
+
+	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+		return digit - 'A' + 0x0A;
+	}
+	return -1;
+}
+
+static string_t *
+content_type_param_decode(const char *value)
+{
+	const unsigned char *p, *plast;
+
+	string_t *str = t_str_new(64);
+	plast = p = (const unsigned char *)value;
+	while ( *p != '\0' ) {
+		unsigned char ch;
+		int digit;
+
+		if ( *p == '%' ) {
+			if ( p - plast > 0 )
+				str_append_data(str, plast, (p - plast));
+			p++;
+			if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+				return NULL;
+			ch = (unsigned char)digit;
+			p++;
+			if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+				return NULL;
+			ch = (ch << 4) + (unsigned char)digit;
+			str_append_data(str, &ch, 1);
+			plast = p + 1;
+		}
+		p++;
+	}
+	if ( p - plast > 0 )
+		str_append_data(str, plast, (p - plast));
+	return str;
+}
+
+static string_t *
+content_type_param_next(struct content_header_stringlist *strlist)
+{
+	const char *const *values = strlist->param_values;
+
+	i_assert( strlist->params != NULL );
+
+	for ( ; *values != NULL; values += 2 ) {
+		const char *const *params = strlist->params;
+		const char *name = values[0], *value = values[1];
+		size_t nlen = strlen(name);
+
+		for ( ; *params != NULL; params++ ) {
+			size_t plen = strlen(*params);
+
+			if ( plen != nlen &&
+				(nlen != plen + 1 || name[nlen-1] != '*') )
+				continue;
+
+			if ( plen == nlen ) {
+				if ( strcasecmp(name, *params) == 0 ) {
+					strlist->param_values = values + 2;
+					return t_str_new_const(value, strlen(value));
+				}
+			} else {
+				if ( strncasecmp(name, *params, plen) == 0 ) {
+					string_t *result = NULL;
+
+					strlist->param_values = values + 2;
+
+					// FIXME: transcode charset
+					value = strchr(value, '\'');
+					if (value != NULL)
+						value = strchr(value+1, '\'');
+					if (value != NULL)
+						result = content_type_param_decode(value + 1);
+					if (result == NULL)
+						strlist->param_values = NULL;
+					return result;
+				}
+			}
+		}
+	}
+
+	strlist->param_values = NULL;
+	return NULL;
+}
+
+// FIXME: not too happy with the use of string_t like this.
+// Sieve should have a special runtime string type (TODO)
+static string_t *
+content_header_parse(struct content_header_stringlist *strlist,
+	const char *hdr_name, string_t *str)
+{
+	struct rfc822_parser_context parser;
+	const char *type, *p;
+	bool is_ctype = FALSE;
+	string_t *content;
+
+	if ( strlist->option == EXT_MIME_OPTION_NONE )
+		return str;
+
+	if ( strcasecmp(hdr_name, "content-type") == 0 )
+		is_ctype = TRUE;
+	else if ( strcasecmp(hdr_name, "content-disposition") != 0 )
+		return t_str_new(0);
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, str_data(str), str_len(str), NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type/disposition */
+	content = t_str_new(64);
+	if ( is_ctype ){ 
+		if (rfc822_parse_content_type(&parser, content) < 0) {
+			str_truncate(content, 0);
+			return content;
+		}
+	} else {
+		if (rfc822_parse_mime_token(&parser, content) < 0) {
+			str_truncate(content, 0);
+			return content;
+		}
+	}
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' ) {
+		str_truncate(content, 0);
+		return content;
+	}
+
+	if ( strlist->option == EXT_MIME_OPTION_PARAM ) {
+		string_t *param_val;
+
+		i_assert( strlist->params != NULL );
+
+		// FIXME: not very nice when multiple parameters in the same header
+		// are queried in successive tests.
+		str_truncate(content, 0);
+		rfc2231_parse(&parser, &strlist->param_values);
+
+		param_val = content_type_param_next(strlist);
+		if ( param_val != NULL )
+			content = param_val;
+	} else {
+		type = str_c(content);
+		if ( (p=strchr(type, '/')) == NULL ) {
+			i_assert( !is_ctype );
+			if ( strlist->option == EXT_MIME_OPTION_SUBTYPE )
+				str_truncate(content, 0);
+		} else {
+			i_assert( is_ctype );
+			if ( strlist->option == EXT_MIME_OPTION_TYPE )
+				str_truncate(content, (p - type));
+			else if ( strlist->option == EXT_MIME_OPTION_SUBTYPE )
+				str_delete(content, 0, (p - type) + 1);
+		}
+	}
+
+	/* Success */
+	return content;
+}
+
+static int content_header_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	const char *hdr_name;
+	int ret;
+
+	if ( strlist->param_values != NULL ) {
+		string_t *param_val;
+
+		i_assert( strlist->option == EXT_MIME_OPTION_PARAM );
+		param_val = content_type_param_next(strlist);
+		if ( param_val != NULL ) {
+			*str_r = param_val;
+			return 1;
+		}
+	}
+
+	if ( (ret=sieve_header_list_next_item
+		(strlist->source, &hdr_name, str_r)) <= 0 ) {
+		if (ret < 0) {
+			_strlist->exec_status =
+				strlist->source->strlist.exec_status;
+		}
+		return ret;
+	}
+
+	*str_r = content_header_parse(strlist, hdr_name, *str_r);
+	return 1;
+}
+
+static void content_header_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	sieve_header_list_reset(strlist->source);
+}
+
+static int content_header_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	return sieve_header_list_get_length(strlist->source);
+}
+
+static void content_header_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	sieve_header_list_set_trace(strlist->source, trace);
+}
+
+/*
+ * Header override implementation
+ */
+
+/* Context data */
+
+struct svmo_mime_context {
+	enum ext_mime_option mimeopt;
+	const char *const *params;
+	unsigned int anychild:1;
+};
+
+/* Context coding */
+
+static bool svmo_mime_dump_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int anychild, mimeopt;
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &anychild) )
+		return FALSE;
+	if ( anychild > 0 )
+		sieve_code_dumpf(denv, "anychild");
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &mimeopt) )
+		return FALSE;
+
+	switch ( mimeopt ) {
+	case EXT_MIME_OPTION_NONE:
+		break;
+	case EXT_MIME_OPTION_TYPE:
+		sieve_code_dumpf(denv, "option: type");
+		break;
+	case EXT_MIME_OPTION_SUBTYPE:
+		sieve_code_dumpf(denv, "option: subtype");
+		break;
+	case EXT_MIME_OPTION_CONTENTTYPE:
+		sieve_code_dumpf(denv, "option: contenttype");
+		break;
+	case EXT_MIME_OPTION_PARAM:
+		sieve_code_dumpf(denv, "option: param");
+		sieve_code_descend(denv);
+		if ( !sieve_opr_stringlist_dump(denv, address, "param-list") )
+			return FALSE;
+		sieve_code_ascend(denv);
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int svmo_mime_read_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **ho_context)
+{
+	pool_t pool = sieve_result_pool(renv->result); // FIXME: investigate
+	struct svmo_mime_context *ctx;
+	unsigned int anychild = 0, mimeopt = EXT_MIME_OPTION_NONE;
+	struct sieve_stringlist *param_list = NULL;
+	int ret;
+
+	if ( !sieve_binary_read_byte
+		(renv->sblock, address, &anychild) ) {
+		sieve_runtime_trace_error(renv,
+			"anychild: invalid byte");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_byte
+		(renv->sblock, address, &mimeopt) ||
+		mimeopt > EXT_MIME_OPTION_PARAM ) {
+		sieve_runtime_trace_error(renv,
+			"option: invalid mime option code");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( mimeopt == EXT_MIME_OPTION_PARAM &&
+		(ret=sieve_opr_stringlist_read
+			(renv, address, "param-list", &param_list)) <= 0 )
+		return ret;
+
+	ctx = p_new(pool, struct svmo_mime_context, 1);
+	ctx->anychild = (anychild == 0 ? FALSE : TRUE);
+	ctx->mimeopt = (enum ext_mime_option)mimeopt;
+
+	if ( param_list != NULL && sieve_stringlist_read_all
+		(param_list, pool, &ctx->params) < 0 ) {
+		sieve_runtime_trace_error(renv,
+			"failed to read param-list operand");
+		return param_list->exec_status;
+	}
+
+	*ho_context = (void *) ctx;
+	return SIEVE_EXEC_OK;
+}
+
+/* Override */
+
+static int svmo_mime_header_override
+(const struct sieve_message_override *svmo,
+	const struct sieve_runtime_env *renv, bool mime_decode,
+	struct sieve_stringlist **headers_r)
+{
+	struct svmo_mime_context *ctx =
+		(struct svmo_mime_context *)svmo->context;
+	struct ext_foreverypart_runtime_loop *sfploop;
+	struct sieve_header_list *headers;
+	struct sieve_stringlist *values;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+		"header mime override:");
+	sieve_runtime_trace_descend(renv);
+
+	if ( ctx->anychild ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"headers from current mime part and children");
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"headers from current mime part");
+	}
+
+	sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+	if ( sfploop == NULL ) {
+		headers = sieve_message_header_list_create
+			(renv, *headers_r, mime_decode);
+	} else {
+		headers = sieve_mime_header_list_create
+			(renv, *headers_r, &sfploop->part_iter,
+				mime_decode, ctx->anychild);
+	}
+	values = &headers->strlist;
+
+	switch ( ctx->mimeopt ) {
+	case EXT_MIME_OPTION_NONE:
+		break;
+	case EXT_MIME_OPTION_TYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime type from header value");
+		break;
+	case EXT_MIME_OPTION_SUBTYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime subtype from header value");
+		break;
+	case EXT_MIME_OPTION_CONTENTTYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime contenttype from header value");
+		break;
+	case EXT_MIME_OPTION_PARAM:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime parameters from header value");
+		break;
+	default:
+		i_unreached();
+	}
+
+	if ( ctx->mimeopt != EXT_MIME_OPTION_NONE ) {
+		values = content_header_stringlist_create
+			(renv, headers, ctx->mimeopt, ctx->params);
+	}
+	*headers_r = values;
+
+	sieve_runtime_trace_ascend(renv);
+	return SIEVE_EXEC_OK;
+}
+
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index d3d022f856a803081d78e238d197ab9521b13b52..4f75420b09522ea8d585e7ebc102875e20ae758a 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -106,6 +106,8 @@ extern const struct sieve_extension_def date_extension;
 extern const struct sieve_extension_def index_extension;
 extern const struct sieve_extension_def ihave_extension;
 extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def mime_extension;
+extern const struct sieve_extension_def foreverypart_extension;
 extern const struct sieve_extension_def mboxmetadata_extension;
 extern const struct sieve_extension_def servermetadata_extension;
 
@@ -121,7 +123,7 @@ const struct sieve_extension_def *sieve_core_extensions[] = {
 	&copy_extension, &include_extension, &body_extension,
 	&variables_extension, &enotify_extension, &environment_extension,
 	&mailbox_extension, &date_extension, &index_extension, &ihave_extension,
-	&duplicate_extension
+	&duplicate_extension, &mime_extension, &foreverypart_extension
 };
 
 const unsigned int sieve_core_extensions_count =
diff --git a/tests/extensions/mime/content-header.svtest b/tests/extensions/mime/content-header.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..9686e35443e03da6a2b6ab31ed005b73c52b1a36
--- /dev/null
+++ b/tests/extensions/mime/content-header.svtest
@@ -0,0 +1,161 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "mime";
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/plain
+
+Frop
+.
+;
+
+test "Simple Content-Type :type" {
+	if not header :mime :type "content-type" "text" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Simple Content-Type :subype" {
+	if not header :mime :subtype "content-type" "plain" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Simple Content-Type :contenttype" {
+	if not header :mime :contenttype "content-type" "text/plain" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/calendar; method=request; charset=UTF-8;
+
+Frop
+.
+;
+
+test "Advanced Content-Type :type" {
+	if not header :mime :type "content-type" "text" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Advanced Content-Type :subype" {
+	if not header :mime :subtype "content-type" "calendar" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Advanced Content-Type :contenttype" {
+	if not header :mime :contenttype "content-type" "text/calendar" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test "Advanced Content-Type :param" {
+	if not header :mime :param "method" "content-type" "request" {
+		test_fail "wrong method param extracted";
+	}
+
+	if not header :mime :param "charset" "content-type" "UTF-8" {
+		test_fail "wrong charset param extracted";
+	}
+
+	if not header :mime :param ["method", "charset"]
+		"content-type" "request" {
+		test_fail "wrong method param extracted";
+	}
+
+	if not header :mime :param ["method", "charset"]
+		"content-type" "UTF-8" {
+		test_fail "wrong charset param extracted";
+	}
+
+	if not header :count "eq" :mime :param ["method", "charset"]
+		"content-type" "2" {
+		test_fail "wrong number of parameters";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*2="isn't it!"
+
+Frop
+.
+;
+
+test "Encoded Content-Type :param" {
+	if not header :mime :param "title" "content-type"
+		"This is even more ***fun*** isn't it!" {
+		test_fail "wrong method param extracted";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: image/png
+Content-Disposition: inline; filename="frop.exe"; title="Frop!"
+
+Frop
+.
+;
+
+test "Content-Disposition :type" {
+	if not header :mime :type "content-disposition" "inline" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Content-Disposition :subype" {
+	if not header :mime :subtype "content-disposition" "" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Content-Disposition :contenttype" {
+	if not header :mime :contenttype "content-disposition" "inline" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test "Content-Disposition :param" {
+	if not header :mime :param "filename" "content-disposition" "frop.exe" {
+		test_fail "wrong filename param extracted";
+	}
+
+	if not header :mime :param "title" "content-disposition" "Frop!" {
+		test_fail "wrong title param extracted";
+	}
+
+	if not header :mime :param ["filename", "title"]
+		"content-disposition" "frop.exe" {
+		test_fail "wrong filename param extracted";
+	}
+
+	if not header :mime :param ["filename", "title"]
+		"content-disposition" "Frop!" {
+		test_fail "wrong title param extracted";
+	}
+
+	if not header :count "eq" :mime :param ["filename", "title"]
+		"content-disposition" "2" {
+		test_fail "wrong number of parameters";
+	}
+
+}
+
+
diff --git a/tests/extensions/mime/errors.svtest b/tests/extensions/mime/errors.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..979da095372ad9a392cc349f7d3e55699d27e5c6
--- /dev/null
+++ b/tests/extensions/mime/errors.svtest
@@ -0,0 +1,54 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Foreverypart command" {
+	if test_script_compile "errors/foreverypart.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "12" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Break command" {
+	if test_script_compile "errors/break.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "21" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Header test with :mime tag" {
+	if test_script_compile "errors/header-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "10" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Address test with :mime tag" {
+	if test_script_compile "errors/address-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Exists test with :mime tag" {
+	if test_script_compile "errors/exists-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
diff --git a/tests/extensions/mime/errors/address-mime-tag.sieve b/tests/extensions/mime/errors/address-mime-tag.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..7adb7bcc5918ff0723f4bf55a52694d989540081
--- /dev/null
+++ b/tests/extensions/mime/errors/address-mime-tag.sieve
@@ -0,0 +1,38 @@
+require "mime";
+
+## Address
+
+# No error
+if address :contains :mime "To" "frop@example.com" {
+	discard;
+}
+
+# No error
+if address :anychild :contains :mime "To" "frop@example.com" {
+	discard;
+}
+
+# 1: Bare anychild option
+if address :anychild "To" "frop@example.com" {
+	discard;
+}
+
+# 2: Inappropriate option
+if address :mime :anychild :type "To" "frop@example.com" {
+	discard;
+}
+
+# 3: Inappropriate option
+if address :mime :anychild :subtype "To" "frop@example.com" {
+	discard;
+}
+
+# 4: Inappropriate option
+if address :mime :anychild :contenttype "To" "frop@example.com" {
+	discard;
+}
+
+# 5: Inappropriate option
+if address :mime :anychild :param "frop" "To" "frop@example.com" {
+	discard;
+}
diff --git a/tests/extensions/mime/errors/break.sieve b/tests/extensions/mime/errors/break.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..1858673513f3772324231e391abfc2f46a8730ae
--- /dev/null
+++ b/tests/extensions/mime/errors/break.sieve
@@ -0,0 +1,157 @@
+require "foreverypart";
+
+foreverypart :name "frop" {
+	# 1: Spurious tag
+	break :tag;
+
+	# 2: Spurious tests
+	break true;
+
+	# 3: Spurious tests
+	break anyof(true, false);
+
+	# 4: Bare string
+	break "frop";
+
+	# 5: Bare string-list
+	break ["frop", "friep"];
+
+	# 6: Several bad arguments
+	break 13 ["frop", "friep"];
+
+	# 7: Spurious additional tag
+	break :name "frop" :friep;
+
+	# 8: Spurious additional string
+	break :name "frop" "friep";
+
+	# 9: Bad name
+	break :name 13;
+
+	# 10: Bad name
+	break :name ["frop", "friep"];
+
+	# No error
+	break;
+
+	# No error
+	break :name "frop";
+
+	# No error
+	if exists "frop" {
+		break;
+	}
+
+	# No error
+	if exists "frop" {
+		break :name "frop";
+	}
+
+	# No error	
+	foreverypart {
+		break :name "frop";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break :name "frop";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break :name "friep";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break;
+	}
+
+	# No error	
+	foreverypart {
+		if exists "frop" {
+			break :name "frop";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break :name "frop";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break :name "friep";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break;
+		}
+	}
+}
+
+# 11: Outside loop
+break; 
+
+# 12: Outside loop
+if exists "frop" {
+	break;
+}
+
+# 13: Outside loop
+break :name "frop";
+
+# 14: Outside loop
+if exists "frop" {
+	break :name "frop";
+}
+
+# 15: Bad name
+foreverypart {
+	break :name "frop";
+}
+
+# 16: Bad name
+foreverypart {
+	if exists "frop" {
+		break :name "frop";
+	}
+}
+
+# 17: Bad name
+foreverypart :name "friep" {
+	break :name "frop";
+}
+
+# 18: Bad name
+foreverypart :name "friep" {
+	if exists "frop" {
+		break :name "frop";
+	}
+}
+
+# 19: Bad name
+foreverypart :name "friep" {
+	foreverypart :name "frop" {
+		break :name "frml";
+	}
+}
+
+# 20: Bad name
+foreverypart :name "friep" {
+	foreverypart :name "frop" {
+		if exists "frop" {
+			break :name "frml";
+		}
+	}
+}
+
+
+
+
diff --git a/tests/extensions/mime/errors/exists-mime-tag.sieve b/tests/extensions/mime/errors/exists-mime-tag.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..84c86a777874ca690d45fa4ceb038cc887d48b55
--- /dev/null
+++ b/tests/extensions/mime/errors/exists-mime-tag.sieve
@@ -0,0 +1,43 @@
+require "mime";
+
+## Exists
+
+# No error
+if exists :mime "To" {
+	discard;
+}
+
+# No error
+if exists :anychild :mime "To" {
+	discard;
+}
+
+# 1: Inappropriate option
+if exists :anychild "To" {
+	discard;
+}
+
+# 2: Inappropriate option
+if exists :mime :type "To" {
+	discard;
+}
+
+# 3: Inappropriate option
+if exists :mime :subtype "To" {
+	discard;
+}
+
+# 4: Inappropriate option
+if exists :mime :contenttype "To" {
+	discard;
+}
+
+# 5: Inappropriate option
+if exists :mime :param ["frop", "friep"] "To" {
+	discard;
+}
+
+
+
+
+
diff --git a/tests/extensions/mime/errors/foreverypart.sieve b/tests/extensions/mime/errors/foreverypart.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..38a28d4965e3bf3117081b73e842bc031f47d124
--- /dev/null
+++ b/tests/extensions/mime/errors/foreverypart.sieve
@@ -0,0 +1,45 @@
+require "foreverypart";
+
+# 1: No block
+foreverypart;
+
+# 2: Spurious tag
+foreverypart :tag { }
+
+# 3: Spurious tests
+foreverypart true { }
+
+# 4: Spurious tests
+foreverypart anyof(true, false) { }
+
+# 5: Bare string
+foreverypart "frop" { }
+
+# 6: Bare string-list
+foreverypart ["frop", "friep"] { }
+
+# 7: Several bad arguments
+foreverypart 13 ["frop", "friep"] { }
+
+# 8: Spurious additional tag
+foreverypart :name "frop" :friep { }
+
+# 9: Spurious additional string
+foreverypart :name "frop" "friep" { }
+
+# 10: Bad name
+foreverypart :name 13 { }
+
+# 11: Bad name
+foreverypart :name ["frop", "friep"] { }
+
+# No error
+foreverypart { keep; }
+
+# No error
+foreverypart :name "frop" { keep; }
+
+# No error
+foreverypart :name "frop" { foreverypart { keep; } }
+
+
diff --git a/tests/extensions/mime/errors/header-mime-tag.sieve b/tests/extensions/mime/errors/header-mime-tag.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..85782af406227a9e557d881f9c053906e972b332
--- /dev/null
+++ b/tests/extensions/mime/errors/header-mime-tag.sieve
@@ -0,0 +1,100 @@
+require "mime";
+
+## Header
+
+# No error
+if header :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :type "Content-Type" "text" {
+	discard;
+}
+
+# No error
+if header :mime :subtype "Content-Type" "plain" {
+	discard;
+}
+
+# No error
+if header :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+	discard;
+}
+
+# No error
+if header :anychild :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :anychild :type "Content-Type" "text" {
+	discard;
+}
+
+# No error
+if header :mime :subtype :anychild "Content-Type" "plain" {
+	discard;
+}
+
+# No error
+if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 1: Bare anychild option
+if header :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 2: Bare mime option
+if header :type "Content-Type" "frml" {
+	discard;
+}
+
+# 3: Bare mime option
+if header :subtype "Content-Type" "frml" {
+	discard;
+}
+
+# 4: Bare mime option
+if header :contenttype "Content-Type" "frml" {
+	discard;
+}
+
+# 5: Bare mime option
+if header :param "frop" "Content-Type" "frml" {
+	discard;
+}
+
+# 6: Multiple option tags
+if header :mime :type :subtype "Content-Type" "frml" {
+	discard;
+}
+
+# 7: Bad param argument
+if header :mime :param 13 "Content-Type" "frml" {
+	discard;
+}
+
+# 8: Missing param argument
+if header :mime :param :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 9: Missing param argument
+if header :mime :param :frop "Content-Type" "frml" {
+	discard;
+}
+
+
diff --git a/tests/extensions/mime/execute.svtest b/tests/extensions/mime/execute.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..701d817a4ee45dfc281635e5ebef00392436f983
--- /dev/null
+++ b/tests/extensions/mime/execute.svtest
@@ -0,0 +1,23 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test "Basic" {
+	if not test_script_compile "execute/ihave.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+
+        test_binary_save "ihave-basic";
+        test_binary_load "ihave-basic";
+}
+
diff --git a/tests/extensions/mime/execute/foreverypart.sieve b/tests/extensions/mime/execute/foreverypart.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..0fe84c8278b9cb8d997b6f9ac8db9139f88fc754
--- /dev/null
+++ b/tests/extensions/mime/execute/foreverypart.sieve
@@ -0,0 +1,7 @@
+require "ihave";
+
+if ihave "nonsense-extension" {
+	nonsense_command "Frop!";
+}
+
+redirect "frop@example.com";