summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/sieve-message.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-message.c')
-rw-r--r--pigeonhole/src/lib-sieve/sieve-message.c1845
1 files changed, 1845 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-message.c b/pigeonhole/src/lib-sieve/sieve-message.c
new file mode 100644
index 0000000..d086883
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-message.c
@@ -0,0 +1,1845 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mempool.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "time-util.h"
+#include "rfc822-parser.h"
+#include "message-date.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "message-header-decode.h"
+#include "mail-html2text.h"
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "smtp-params.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "raw-storage.h"
+
+#include "edit-mail.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-address-parts.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-match.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-message.h"
+
+/*
+ * Message transmission
+ */
+
+const char *sieve_message_get_new_id(const struct sieve_instance *svinst)
+{
+ static int count = 0;
+
+ return t_strdup_printf("<dovecot-sieve-%s-%s-%d@%s>",
+ dec2str(ioloop_timeval.tv_sec), dec2str(ioloop_timeval.tv_usec),
+ count++, svinst->hostname);
+}
+
+/*
+ * Message context
+ */
+
+struct sieve_message_header {
+ const char *name;
+
+ const unsigned char *value, *utf8_value;
+ size_t value_len, utf8_value_len;
+};
+
+struct sieve_message_part {
+ struct sieve_message_part *parent, *next, *children;
+
+ ARRAY(struct sieve_message_header) headers;
+
+ const char *content_type;
+ const char *content_disposition;
+
+ const char *decoded_body;
+ const char *text_body;
+ size_t decoded_body_size;
+ size_t text_body_size;
+
+ bool have_body:1; /* there's the empty end-of-headers line */
+ bool epilogue:1; /* this is a multipart epilogue */
+};
+
+struct sieve_message_version {
+ struct mail *mail;
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct edit_mail *edit_mail;
+};
+
+struct sieve_message_context {
+ pool_t pool;
+ pool_t context_pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+ struct timeval time;
+
+ struct mail_user *mail_user;
+ const struct sieve_message_data *msgdata;
+
+ /* Message versioning */
+
+ struct mail_user *raw_mail_user;
+ ARRAY(struct sieve_message_version) versions;
+
+ /* Context data for extensions */
+
+ ARRAY(void *) ext_contexts;
+
+ /* Body */
+
+ ARRAY(struct sieve_message_part *) cached_body_parts;
+ ARRAY(struct sieve_message_part_data) return_body_parts;
+ buffer_t *raw_body;
+
+ bool edit_snapshot:1;
+ bool substitute_snapshot:1;
+};
+
+/*
+ * Message versions
+ */
+
+static inline struct sieve_message_version *sieve_message_version_new
+(struct sieve_message_context *msgctx)
+{
+ return array_append_space(&msgctx->versions);
+}
+
+static inline struct sieve_message_version *sieve_message_version_get
+(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *versions;
+ unsigned int count;
+
+ versions = array_get_modifiable(&msgctx->versions, &count);
+ if ( count == 0 )
+ return array_append_space(&msgctx->versions);
+
+ return &versions[count-1];
+}
+
+static inline void sieve_message_version_free
+(struct sieve_message_version *version)
+{
+ if ( version->edit_mail != NULL ) {
+ edit_mail_unwrap(&version->edit_mail);
+ version->edit_mail = NULL;
+ }
+
+ if ( version->mail != NULL ) {
+ mail_free(&version->mail);
+ mailbox_transaction_rollback(&version->trans);
+ mailbox_free(&version->box);
+ version->mail = NULL;
+ }
+}
+
+/*
+ * Message context object
+ */
+
+struct sieve_message_context *sieve_message_context_create
+(struct sieve_instance *svinst, struct mail_user *mail_user,
+ const struct sieve_message_data *msgdata)
+{
+ struct sieve_message_context *msgctx;
+
+ msgctx = i_new(struct sieve_message_context, 1);
+ msgctx->refcount = 1;
+ msgctx->svinst = svinst;
+
+ msgctx->mail_user = mail_user;
+ msgctx->msgdata = msgdata;
+
+ i_gettimeofday(&msgctx->time);
+
+ sieve_message_context_reset(msgctx);
+
+ return msgctx;
+}
+
+void sieve_message_context_ref(struct sieve_message_context *msgctx)
+{
+ msgctx->refcount++;
+}
+
+static void sieve_message_context_clear(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *versions;
+ unsigned int count, i;
+
+ if ( msgctx->pool != NULL ) {
+ versions = array_get_modifiable(&msgctx->versions, &count);
+
+ for ( i = 0; i < count; i++ ) {
+ sieve_message_version_free(&versions[i]);
+ }
+
+ pool_unref(&(msgctx->pool));
+ }
+}
+
+void sieve_message_context_unref(struct sieve_message_context **msgctx)
+{
+ i_assert((*msgctx)->refcount > 0);
+
+ if (--(*msgctx)->refcount != 0)
+ return;
+
+ if ( (*msgctx)->raw_mail_user != NULL )
+ mail_user_unref(&(*msgctx)->raw_mail_user);
+
+ sieve_message_context_clear(*msgctx);
+
+ if ( (*msgctx)->context_pool != NULL )
+ pool_unref(&((*msgctx)->context_pool));
+
+ i_free(*msgctx);
+ *msgctx = NULL;
+}
+
+static void sieve_message_context_flush(struct sieve_message_context *msgctx)
+{
+ pool_t pool;
+
+ if ( msgctx->context_pool != NULL )
+ pool_unref(&(msgctx->context_pool));
+
+ msgctx->context_pool = pool =
+ pool_alloconly_create("sieve_message_context_data", 2048);
+
+ p_array_init(&msgctx->ext_contexts, pool,
+ sieve_extensions_get_count(msgctx->svinst));
+
+ p_array_init(&msgctx->cached_body_parts, pool, 8);
+ p_array_init(&msgctx->return_body_parts, pool, 8);
+ msgctx->raw_body = NULL;
+}
+
+void sieve_message_context_reset(struct sieve_message_context *msgctx)
+{
+ sieve_message_context_clear(msgctx);
+
+ msgctx->pool = pool_alloconly_create("sieve_message_context", 1024);
+
+ p_array_init(&msgctx->versions, msgctx->pool, 4);
+
+ sieve_message_context_flush(msgctx);
+}
+
+pool_t sieve_message_context_pool(struct sieve_message_context *msgctx)
+{
+ return msgctx->context_pool;
+}
+
+void sieve_message_context_time(struct sieve_message_context *msgctx,
+ struct timeval *time)
+{
+ *time = msgctx->time;
+}
+
+/* Extension support */
+
+void sieve_message_context_extension_set
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext,
+ void *context)
+{
+ if ( ext->id < 0 ) return;
+
+ array_idx_set(&msgctx->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *sieve_message_context_extension_get
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext)
+{
+ void * const *ctx;
+
+ if ( ext->id < 0 || ext->id >= (int) array_count(&msgctx->ext_contexts) )
+ return NULL;
+
+ ctx = array_idx(&msgctx->ext_contexts, (unsigned int) ext->id);
+
+ return *ctx;
+}
+
+/* Envelope */
+
+const struct smtp_address *sieve_message_get_orig_recipient
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *orcpt_to = NULL;
+
+ if ( msgdata->envelope.rcpt_params != NULL ) {
+ orcpt_to = msgdata->envelope.rcpt_params->orcpt.addr;
+ if ( !smtp_address_isnull(orcpt_to) )
+ return orcpt_to;
+ }
+
+ orcpt_to = msgdata->envelope.rcpt_to;
+ return ( !smtp_address_isnull(orcpt_to) ? orcpt_to : NULL );
+}
+
+const struct smtp_address *sieve_message_get_final_recipient
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *rcpt_to = msgdata->envelope.rcpt_to;
+
+ return ( !smtp_address_isnull(rcpt_to) ? rcpt_to : NULL);
+}
+
+const struct smtp_address *sieve_message_get_sender
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *mail_from = msgdata->envelope.mail_from;
+
+ return ( !smtp_address_isnull(mail_from) ? mail_from : NULL);
+}
+
+/*
+ * Mail
+ */
+
+int sieve_message_substitute
+(struct sieve_message_context *msgctx, struct istream *input)
+{
+ static const char *wanted_headers[] = {
+ "From", "Message-ID", "Subject", "Return-Path", NULL
+ };
+ static const struct smtp_address default_sender = {
+ .localpart = DEFAULT_ENVELOPE_SENDER,
+ .domain = NULL,
+ };
+ struct mail_user *mail_user = msgctx->mail_user;
+ struct sieve_message_version *version;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ struct mailbox *box = NULL;
+ const struct smtp_address *sender;
+ int ret;
+
+ i_assert(input->blocking);
+
+ if ( msgctx->raw_mail_user == NULL ) {
+ void **sets = master_service_settings_get_others(master_service);
+
+ msgctx->raw_mail_user =
+ raw_storage_create_from_set(mail_user->set_info, sets[0]);
+ }
+
+ i_stream_seek(input, 0);
+ sender = sieve_message_get_sender(msgctx);
+ sender = (sender == NULL ? &default_sender : sender);
+ ret = raw_mailbox_alloc_stream(msgctx->raw_mail_user, input, (time_t)-1,
+ smtp_address_encode(sender), &box);
+
+ if ( ret < 0 ) {
+ e_error(msgctx->svinst->event,
+ "can't open substituted mail as raw: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ if ( msgctx->substitute_snapshot ) {
+ version = sieve_message_version_new(msgctx);
+ } else {
+ version = sieve_message_version_get(msgctx);
+ sieve_message_version_free(version);
+ }
+
+ version->box = box;
+ version->trans = mailbox_transaction_begin(box, 0, __func__);
+ headers_ctx = mailbox_header_lookup_init(box, wanted_headers);
+ version->mail = mail_alloc(version->trans, 0, headers_ctx);
+ mailbox_header_lookup_unref(&headers_ctx);
+ mail_set_seq(version->mail, 1);
+
+ sieve_message_context_flush(msgctx);
+
+ msgctx->substitute_snapshot = FALSE;
+ msgctx->edit_snapshot = FALSE;
+
+ return 1;
+}
+
+struct mail *sieve_message_get_mail
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_version *versions;
+ unsigned int count;
+
+ versions = array_get(&msgctx->versions, &count);
+ if ( count == 0 )
+ return msgctx->msgdata->mail;
+
+ if ( versions[count-1].edit_mail != NULL )
+ return edit_mail_get_mail(versions[count-1].edit_mail);
+
+ return versions[count-1].mail;
+}
+
+struct edit_mail *sieve_message_edit
+(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *version;
+
+ version = sieve_message_version_get(msgctx);
+
+ if ( version->edit_mail == NULL ) {
+ version->edit_mail = edit_mail_wrap
+ (( version->mail == NULL ? msgctx->msgdata->mail : version->mail ));
+ } else if ( msgctx->edit_snapshot ) {
+ version->edit_mail = edit_mail_snapshot(version->edit_mail);
+ }
+
+ msgctx->edit_snapshot = FALSE;
+
+ return version->edit_mail;
+}
+
+void sieve_message_snapshot
+(struct sieve_message_context *msgctx)
+{
+ msgctx->edit_snapshot = TRUE;
+ msgctx->substitute_snapshot = TRUE;
+}
+
+/*
+ * Message header list
+ */
+
+/* Forward declarations */
+
+static int sieve_message_header_list_next_item
+ (struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r);
+static int sieve_message_header_list_next_value
+ (struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_message_header_list_reset
+ (struct sieve_stringlist *_strlist);
+
+/* String list object */
+
+struct sieve_message_header_list {
+ struct sieve_header_list hdrlist;
+
+ struct sieve_stringlist *field_names;
+
+ const char *header_name;
+ const char *const *headers;
+ int headers_index;
+
+ bool mime_decode:1;
+};
+
+struct sieve_header_list *sieve_message_header_list_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ bool mime_decode)
+{
+ struct sieve_message_header_list *hdrlist;
+
+ hdrlist = t_new(struct sieve_message_header_list, 1);
+ hdrlist->hdrlist.strlist.runenv = renv;
+ hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ hdrlist->hdrlist.strlist.next_item = sieve_message_header_list_next_value;
+ hdrlist->hdrlist.strlist.reset = sieve_message_header_list_reset;
+ hdrlist->hdrlist.next_item = sieve_message_header_list_next_item;
+ hdrlist->field_names = field_names;
+ hdrlist->mime_decode = mime_decode;
+
+ return &hdrlist->hdrlist;
+}
+
+// NOTE: get rid of this once we have a proper Sieve string type
+static inline string_t *_header_right_trim(const char *raw)
+{
+ string_t *result;
+ const char *p, *pend;
+
+ pend = raw + strlen(raw);
+ if (raw == pend) {
+ result = t_str_new(1);
+ } else {
+ for ( p = pend-1; p >= raw; p-- ) {
+ if ( *p != ' ' && *p != '\t' ) break;
+ }
+ result = t_str_new(p - raw + 1);
+ str_append_data(result, raw, p - raw + 1);
+ }
+ return result;
+}
+
+/* String list implementation */
+
+static int sieve_message_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r)
+{
+ struct sieve_message_header_list *hdrlist =
+ (struct sieve_message_header_list *) _hdrlist;
+ const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+
+ if ( name_r != NULL )
+ *name_r = NULL;
+ *value_r = NULL;
+
+ /* Check for end of current header list */
+ if ( hdrlist->headers == NULL ) {
+ hdrlist->headers_index = 0;
+ } else if ( hdrlist->headers[hdrlist->headers_index] == NULL ) {
+ hdrlist->headers = NULL;
+ hdrlist->headers_index = 0;
+ }
+
+ /* Fetch next header */
+ while ( hdrlist->headers == NULL ) {
+ string_t *hdr_item = NULL;
+ int ret;
+
+ /* Read next header name from source list */
+ if ( (ret=sieve_stringlist_next_item
+ (hdrlist->field_names, &hdr_item)) <= 0 )
+ return ret;
+
+ hdrlist->header_name = str_c(hdr_item);
+
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracting `%s' headers from message",
+ str_sanitize(str_c(hdr_item), 80));
+ }
+
+ /* Fetch all matching headers from the e-mail */
+ if ( hdrlist->mime_decode ) {
+ ret = mail_get_headers_utf8(mail,
+ str_c(hdr_item), &hdrlist->headers);
+ } else {
+ ret = mail_get_headers(mail,
+ str_c(hdr_item), &hdrlist->headers);
+ }
+
+ if (ret < 0) {
+ _hdrlist->strlist.exec_status =
+ sieve_runtime_mail_error(renv, mail,
+ "failed to read header field `%s'", str_c(hdr_item));
+ return -1;
+ }
+
+ if ( ret == 0 || hdrlist->headers[0] == NULL ) {
+ /* Try next item when no headers found */
+ hdrlist->headers = NULL;
+ }
+ }
+
+ /* Return next item */
+ if ( name_r != NULL )
+ *name_r = hdrlist->header_name;
+ *value_r = _header_right_trim(hdrlist->headers[hdrlist->headers_index++]);
+ return 1;
+}
+
+static int sieve_message_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+ struct sieve_header_list *hdrlist =
+ (struct sieve_header_list *) _strlist;
+
+ return sieve_message_header_list_next_item
+ (hdrlist, NULL, value_r);
+}
+
+static void sieve_message_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+ struct sieve_message_header_list *hdrlist =
+ (struct sieve_message_header_list *) strlist;
+
+ hdrlist->headers = NULL;
+ hdrlist->headers_index = 0;
+ sieve_stringlist_reset(hdrlist->field_names);
+}
+
+/*
+ * Header override operand
+ */
+
+const struct sieve_operand_class sieve_message_override_operand_class =
+ { "header-override" };
+
+bool sieve_opr_message_override_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ struct sieve_message_override svmo;
+ const struct sieve_message_override_def *hodef;
+
+ if ( !sieve_opr_object_dump
+ (denv, &sieve_message_override_operand_class, address, &svmo.object) )
+ return FALSE;
+
+ hodef = svmo.def =
+ (const struct sieve_message_override_def *) svmo.object.def;
+
+ if ( hodef->dump_context != NULL ) {
+ sieve_code_descend(denv);
+ if ( !hodef->dump_context(&svmo, denv, address) ) {
+ return FALSE;
+ }
+ sieve_code_ascend(denv);
+ }
+
+ return TRUE;
+}
+
+int sieve_opr_message_override_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_message_override *svmo)
+{
+ const struct sieve_message_override_def *hodef;
+ int ret;
+
+ svmo->context = NULL;
+
+ if ( !sieve_opr_object_read
+ (renv, &sieve_message_override_operand_class, address, &svmo->object) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ hodef = svmo->def =
+ (const struct sieve_message_override_def *) svmo->object.def;
+
+ if ( hodef->read_context != NULL &&
+ (ret=hodef->read_context(svmo, renv, address, &svmo->context)) <= 0 )
+ return ret;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_message_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while ( opok ) {
+ int opt;
+
+ if ( (opt=sieve_addrmatch_opr_optional_dump
+ (denv, address, opt_code)) <= 0 )
+ return opt;
+
+ if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+ opok = sieve_opr_message_override_dump(denv, address);
+ } else {
+ return ( final ? -1 : 1 );
+ }
+ }
+
+ return -1;
+}
+
+int sieve_message_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code, int *exec_status,
+ struct sieve_address_part *addrp, struct sieve_match_type *mcht,
+ struct sieve_comparator *cmp,
+ ARRAY_TYPE(sieve_message_override) *svmos)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE;
+ int ret;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_OK;
+
+ for ( ;; ) {
+ int opt;
+
+ if ( (opt=sieve_addrmatch_opr_optional_read
+ (renv, address, opt_code, exec_status, addrp, mcht, cmp)) <= 0 )
+ return opt;
+
+ if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+ struct sieve_message_override svmo;
+ const struct sieve_message_override *svmo_idx;
+ unsigned int count, i;
+
+ if ( (ret=sieve_opr_message_override_read
+ (renv, address, &svmo)) <= 0 ) {
+ if ( exec_status != NULL )
+ *exec_status = ret;
+ return -1;
+ }
+
+ if ( !array_is_created(svmos) )
+ t_array_init(svmos, 8);
+ /* insert in sorted sequence */
+ svmo_idx = array_get(svmos, &count);
+ for (i = 0; i < count; i++) {
+ if (svmo.def->sequence < svmo_idx[i].def->sequence) {
+ array_insert(svmos, i, &svmo, 1);
+ break;
+ }
+ }
+ if (count == i)
+ array_append(svmos, &svmo, 1);
+ } else {
+ if ( final ) {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+/*
+ * Message header
+ */
+
+int sieve_message_get_header_fields
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ ARRAY_TYPE(sieve_message_override) *svmos,
+ bool mime_decode, struct sieve_stringlist **fields_r)
+{
+ const struct sieve_message_override *svmo;
+ unsigned int count, i;
+ int ret;
+
+ if ( svmos == NULL || !array_is_created(svmos) ||
+ array_count(svmos) == 0 ) {
+ struct sieve_header_list *headers;
+ headers = sieve_message_header_list_create
+ (renv, field_names, mime_decode);
+ *fields_r = &headers->strlist;
+ return SIEVE_EXEC_OK;
+ }
+
+ svmo = array_get(svmos, &count);
+ if ( svmo[0].def->sequence == 0 &&
+ svmo[0].def->header_override != NULL ) {
+ *fields_r = field_names;
+ } else {
+ struct sieve_header_list *headers;
+ headers = sieve_message_header_list_create
+ (renv, field_names, mime_decode);
+ *fields_r = &headers->strlist;
+ }
+
+ for ( i = 0; i < count; i++ ) {
+ if ( svmo[i].def->header_override != NULL &&
+ (ret=svmo[i].def->header_override
+ (&svmo[i], renv, mime_decode, fields_r)) <= 0 )
+ return ret;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part
+ */
+
+struct sieve_message_part *sieve_message_part_parent
+(struct sieve_message_part *mpart)
+{
+ return mpart->parent;
+}
+
+struct sieve_message_part *sieve_message_part_next
+(struct sieve_message_part *mpart)
+{
+ return mpart->next;
+}
+
+struct sieve_message_part *sieve_message_part_children
+(struct sieve_message_part *mpart)
+{
+ return mpart->children;
+}
+
+const char *sieve_message_part_content_type
+(struct sieve_message_part *mpart)
+{
+ return mpart->content_type;
+}
+
+const char *sieve_message_part_content_disposition
+(struct sieve_message_part *mpart)
+{
+ return mpart->content_disposition;
+}
+
+int sieve_message_part_get_first_header
+(struct sieve_message_part *mpart, const char *field,
+ const char **value_r)
+{
+ const struct sieve_message_header *headers;
+ unsigned int i, count;
+
+ headers = array_get(&mpart->headers, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( strcasecmp( headers[i].name, field) == 0 ) {
+ i_assert( headers[i].value[headers[i].value_len] == '\0' );
+ *value_r = (const char *)headers[i].value;
+ return 1;
+ }
+ }
+
+ *value_r = NULL;
+ return 0;
+}
+
+void sieve_message_part_get_data
+(struct sieve_message_part *mpart,
+ struct sieve_message_part_data *data, bool text)
+{
+ i_zero(data);
+ data->content_type = mpart->content_type;
+ data->content_disposition = mpart->content_disposition;
+
+ if ( !text ) {
+ data->content = mpart->decoded_body;
+ data->size = mpart->decoded_body_size;
+ } else if ( mpart->children != NULL ) {
+ data->content = "";
+ data->size = 0;
+ } else {
+ data->content = mpart->text_body;
+ data->size = mpart->text_body_size;
+ }
+}
+
+/*
+ * Message body
+ */
+
+static void str_replace_nuls(string_t *str)
+{
+ char *data = str_c_modifiable(str);
+ unsigned int i, len = str_len(str);
+
+ for (i = 0; i < len; i++) {
+ if (data[i] == '\0')
+ data[i] = ' ';
+ }
+}
+
+static bool _is_wanted_content_type
+(const char * const *wanted_types, const char *content_type)
+ATTR_NULL(1)
+{
+ const char *subtype;
+ size_t type_len;
+
+ if ( wanted_types == NULL )
+ return TRUE;
+
+ subtype = strchr(content_type, '/');
+ type_len = ( subtype == NULL ? strlen(content_type) :
+ (size_t)(subtype - content_type) );
+
+ i_assert( wanted_types != NULL );
+
+ for (; *wanted_types != NULL; wanted_types++) {
+ const char *wanted_subtype;
+
+ if (**wanted_types == '\0') {
+ /* empty string matches everything */
+ return TRUE;
+ }
+
+ wanted_subtype = strchr(*wanted_types, '/');
+ if (wanted_subtype == NULL) {
+ /* match only main type */
+ if (strlen(*wanted_types) == type_len &&
+ strncasecmp(*wanted_types, content_type, type_len) == 0)
+ return TRUE;
+ } else {
+ /* match whole type/subtype */
+ if (strcasecmp(*wanted_types, content_type) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool sieve_message_body_get_return_parts
+(const struct sieve_runtime_env *renv,
+ const char * const *wanted_types,
+ bool extract_text)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *body_parts;
+ unsigned int i, count;
+ struct sieve_message_part_data *return_part;
+
+ /* Check whether any body parts are cached already */
+ body_parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( count == 0 )
+ return FALSE;
+
+ /* Clear result array */
+ array_clear(&msgctx->return_body_parts);
+
+ /* Fill result array with requested content_types */
+ for (i = 0; i < count; i++) {
+ if (!body_parts[i]->have_body) {
+ /* Part has no body; according to RFC this MUST not match to anything and
+ * therefore it is not included in the result.
+ */
+ continue;
+ }
+
+ /* Skip content types that are not requested */
+ if (!_is_wanted_content_type
+ (wanted_types, body_parts[i]->content_type))
+ continue;
+
+ /* Add new item to the result */
+ return_part = array_append_space(&msgctx->return_body_parts);
+ return_part->content_type = body_parts[i]->content_type;
+ return_part->content_disposition = body_parts[i]->content_disposition;
+
+ /* Depending on whether a decoded body part is requested, the appropriate
+ * cache item is read. If it is missing, this function fails and the cache
+ * needs to be completed by sieve_message_parts_add_missing().
+ */
+ if (extract_text) {
+ if (body_parts[i]->text_body == NULL)
+ return FALSE;
+ return_part->content = body_parts[i]->text_body;
+ return_part->size = body_parts[i]->text_body_size;
+ } else {
+ if (body_parts[i]->decoded_body == NULL)
+ return FALSE;
+ return_part->content = body_parts[i]->decoded_body;
+ return_part->size = body_parts[i]->decoded_body_size;
+ }
+ }
+
+ return TRUE;
+}
+
+static void sieve_message_part_save
+(const struct sieve_runtime_env *renv, buffer_t *buf,
+ struct sieve_message_part *body_part,
+ bool extract_text)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ pool_t pool = msgctx->context_pool;
+ buffer_t *result_buf, *text_buf = NULL;
+ char *part_data;
+ size_t part_size;
+
+ /* Extract text if requested */
+ result_buf = buf;
+ if ( extract_text && body_part->children == NULL &&
+ !body_part->epilogue ) {
+
+ if ( buf->used > 0 && mail_html2text_content_type_match
+ (body_part->content_type) ) {
+ struct mail_html2text *html2text;
+
+ text_buf = buffer_create_dynamic(default_pool, 4096);
+
+ /* Remove HTML markup */
+ html2text = mail_html2text_init(0);
+ mail_html2text_more(html2text, buf->data, buf->used, text_buf);
+ mail_html2text_deinit(&html2text);
+
+ result_buf = text_buf;
+ }
+ }
+
+ /* Add terminating NUL to the body part buffer */
+ buffer_append_c(result_buf, '\0');
+
+ /* Make copy of the buffer */
+ part_data = p_malloc(pool, result_buf->used);
+ memcpy(part_data, result_buf->data, result_buf->used);
+ part_size = result_buf->used - 1;
+
+ /* Free text buffer if used */
+ if ( text_buf != NULL)
+ buffer_free(&text_buf);
+
+ /* Depending on whether the part is processed into text, store message
+ * body in the appropriate cache location.
+ */
+ if ( !extract_text ) {
+ body_part->decoded_body = part_data;
+ body_part->decoded_body_size = part_size;
+ } else {
+ body_part->text_body = part_data;
+ body_part->text_body_size = part_size;
+ }
+
+ /* Clear buffer */
+ buffer_set_used_size(buf, 0);
+}
+
+static const char *
+_parse_content_type(const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_type;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type */
+ content_type = t_str_new(64);
+ if (rfc822_parse_content_type(&parser, content_type) < 0)
+ return "";
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end && *parser.data != ';' )
+ return "";
+
+ /* Success */
+ return str_c(content_type);
+}
+
+static const char *
+_parse_content_disposition(const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_disp;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type */
+ content_disp = t_str_new(64);
+ if (rfc822_parse_mime_token(&parser, content_disp) < 0)
+ return "";
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end && *parser.data != ';' )
+ return "";
+
+ /* Success */
+ return str_c(content_disp);
+}
+
+/* sieve_message_parts_add_missing():
+ * Add requested message body parts to the cache that are missing.
+ */
+static int sieve_message_parts_add_missing
+(const struct sieve_runtime_env *renv,
+ const char *const *content_types,
+ bool extract_text, bool iter_all)
+ ATTR_NULL(2)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ pool_t pool = msgctx->context_pool;
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ struct message_parser_settings mparser_set = {
+ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP,
+ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS,
+ };
+ ARRAY(struct sieve_message_header) headers;
+ struct sieve_message_part *body_part, *header_part, *last_part;
+ struct message_parser_ctx *parser;
+ struct message_decoder_context *decoder;
+ struct message_block block, decoded;
+ struct message_part *mparts, *prev_mpart = NULL;
+ buffer_t *buf;
+ struct istream *input;
+ unsigned int idx = 0;
+ bool save_body = FALSE, have_all;
+ string_t *hdr_content = NULL;
+
+ /* First check whether any are missing */
+ if ( !iter_all && sieve_message_body_get_return_parts
+ (renv, content_types, extract_text) ) {
+ /* Cache hit; all are present */
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Get the message stream */
+ if ( mail_get_stream(mail, NULL, NULL, &input) < 0 ) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to open input message");
+ }
+ if (mail_get_parts(mail, &mparts) < 0) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to parse input message parts");
+ }
+
+ buf = buffer_create_dynamic(default_pool, 4096);
+ body_part = header_part = last_part = NULL;
+
+ if (iter_all) {
+ t_array_init(&headers, 64);
+ hdr_content = t_str_new(512);
+ mparser_set.hdr_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+ } else {
+ i_zero(&headers);
+ }
+
+ /* Initialize body decoder */
+ decoder = message_decoder_init(NULL, 0);
+
+ // FIXME: currently not tested with edit-mail.
+ //parser = message_parser_init_from_parts(parts, input,
+ // hparser_flags, mparser_flags);
+ parser = message_parser_init(pool_datastack_create(),
+ input, &mparser_set);
+ while ( message_parser_parse_next_block(parser, &block) > 0 ) {
+ struct sieve_message_part **body_part_idx;
+ struct message_header_line *hdr = block.hdr;
+ struct sieve_message_header *header;
+ unsigned char *data;
+
+ if ( block.part != prev_mpart ) {
+ bool message_rfc822 = FALSE;
+
+ /* Save previous body part */
+ if ( body_part != NULL ) {
+ /* Treat message/rfc822 separately; headers become content */
+ if ( block.part->parent == prev_mpart &&
+ strcmp(body_part->content_type, "message/rfc822") == 0 ) {
+ message_rfc822 = TRUE;
+ } else {
+ if ( save_body ) {
+ sieve_message_part_save
+ (renv, buf, body_part, extract_text);
+ }
+ }
+ if ( iter_all && !array_is_created(&body_part->headers) &&
+ array_count(&headers) > 0 ) {
+ p_array_init(&body_part->headers, pool, array_count(&headers));
+ array_copy(&body_part->headers.arr, 0,
+ &headers.arr, 0, array_count(&headers));
+ }
+ }
+
+ /* Start processing next part */
+ body_part_idx = array_idx_get_space
+ (&msgctx->cached_body_parts, idx);
+ if ( *body_part_idx == NULL )
+ *body_part_idx = p_new(pool, struct sieve_message_part, 1);
+ body_part = *body_part_idx;
+ body_part->content_type = "text/plain";
+ if ( iter_all )
+ array_clear(&headers);
+
+ /* Copy tree structure */
+ if ( block.part->context != NULL ) {
+ struct sieve_message_part *epipart =
+ (struct sieve_message_part *)block.part->context;
+ i_assert(epipart != NULL);
+
+ /* multipart epilogue */
+ body_part->content_type = epipart->content_type;
+ body_part->have_body = TRUE;
+ body_part->epilogue = TRUE;
+ save_body = iter_all || _is_wanted_content_type
+ (content_types, body_part->content_type);
+
+ } else {
+ struct sieve_message_part *parent = NULL;
+
+ if ( block.part->parent != NULL ) {
+ body_part->parent = parent =
+ (struct sieve_message_part *)
+ block.part->parent->context;
+ }
+
+ /* new part */
+ block.part->context = (void*)body_part;
+
+ if ( last_part != NULL ) {
+ i_assert( parent != NULL );
+ if ( last_part->parent == parent ) {
+ last_part->next = body_part;
+ } else if (parent->children == NULL) {
+ parent->children = body_part;
+ } else {
+ struct sieve_message_part *child = parent->children;
+ while (child->next != NULL && child != body_part)
+ child = child->next;
+ if (child != body_part)
+ child->next = body_part;
+ }
+ }
+ }
+ last_part = body_part;
+
+ /* If this is message/rfc822 content, retain the enveloping part for
+ * storing headers as content.
+ */
+ if ( message_rfc822 ) {
+ i_assert(idx > 0);
+ body_part_idx = array_idx_modifiable
+ (&msgctx->cached_body_parts, idx-1);
+ header_part = *body_part_idx;
+ } else {
+ header_part = NULL;
+ }
+
+ prev_mpart = block.part;
+ idx++;
+ }
+
+ if ( hdr != NULL || block.size == 0 ) {
+ enum {
+ _HDR_CONTENT_TYPE,
+ _HDR_CONTENT_DISPOSITION,
+ _HDR_OTHER
+ } hdr_field;
+
+ /* Reading headers */
+ i_assert( body_part != NULL );
+
+ /* Decode block */
+ (void)message_decoder_decode_next_block
+ (decoder, &block, &decoded);
+
+ /* Check for end of headers */
+ if ( hdr == NULL ) {
+ /* Save headers for message/rfc822 part */
+ if ( header_part != NULL ) {
+ sieve_message_part_save
+ (renv, buf, header_part, FALSE);
+ header_part = NULL;
+ }
+
+ /* Save bodies only if we have a wanted content-type */
+ save_body = iter_all || _is_wanted_content_type
+ (content_types, body_part->content_type);
+ continue;
+ }
+
+ /* Encountered the empty line that indicates the end of the headers and
+ * the start of the body
+ */
+ if ( hdr->eoh ) {
+ body_part->have_body = TRUE;
+ continue;
+ } else if ( header_part != NULL ) {
+ /* Save message/rfc822 header as part content */
+ if ( hdr->continued ) {
+ buffer_append(buf, hdr->value, hdr->value_len);
+ } else {
+ buffer_append(buf, hdr->name, hdr->name_len);
+ buffer_append(buf, hdr->middle, hdr->middle_len);
+ buffer_append(buf, hdr->value, hdr->value_len);
+ }
+ if ( !hdr->no_newline ) {
+ buffer_append(buf, "\r\n", 2);
+ }
+ }
+
+ if ( strcasecmp(hdr->name, "Content-Type" ) == 0 )
+ hdr_field = _HDR_CONTENT_TYPE;
+ else if ( strcasecmp(hdr->name, "Content-Disposition" ) == 0 )
+ hdr_field = _HDR_CONTENT_DISPOSITION;
+ else if ( iter_all && !array_is_created(&body_part->headers) )
+ hdr_field = _HDR_OTHER;
+ else {
+ /* Not interested in this header */
+ continue;
+ }
+
+ /* Header can have folding whitespace. Acquire the full value before
+ * continuing
+ */
+ if ( hdr->continues ) {
+ hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ if ( iter_all && !array_is_created(&body_part->headers) ) {
+ const unsigned char *value, *vp;
+ size_t vlen;
+
+ /* Add header */
+ header = array_append_space(&headers);
+ header->name = p_strdup(pool, hdr->name);
+
+ /* Trim end of field value (not done by parser) */
+ value = hdr->full_value;
+ vp = value + hdr->full_value_len;
+ while ( vp > value &&
+ (vp[-1] == '\t' || vp[-1] == ' ') )
+ vp--;
+ vlen = (size_t)(vp - value);
+
+ /* Decode MIME encoded-words. */
+ str_truncate(hdr_content, 0);
+ message_header_decode_utf8
+ (value, vlen, hdr_content, NULL);
+ if ( vlen != str_len(hdr_content) ||
+ strncmp(str_c(hdr_content), (const char *)value,
+ vlen) != 0 ) {
+ if ( strlen(str_c(hdr_content)) != str_len(hdr_content) ) {
+ /* replace NULs with spaces */
+ str_replace_nuls(hdr_content);
+ }
+ /* store raw */
+ data = p_malloc(pool, vlen + 1);
+ data[vlen] = '\0';
+ header->value = memcpy(data, value, vlen);
+ header->value_len = vlen;
+ /* store decoded */
+ data = p_malloc(pool, str_len(hdr_content) + 1);
+ data[str_len(hdr_content)] = '\0';
+ header->utf8_value = memcpy(data,
+ str_data(hdr_content), str_len(hdr_content));
+ header->utf8_value_len = str_len(hdr_content);
+ } else {
+ /* raw == decoded */
+ data = p_malloc(pool, vlen + 1);
+ data[vlen] = '\0';
+ header->value = header->utf8_value =
+ memcpy(data, value, vlen);
+ header->value_len = header->utf8_value_len = vlen;
+ }
+
+ if ( hdr_field == _HDR_OTHER )
+ continue;
+ }
+
+ /* Parse the content type from the Content-type header */
+ T_BEGIN {
+ switch ( hdr_field ) {
+ case _HDR_CONTENT_TYPE:
+ body_part->content_type =
+ p_strdup(pool, _parse_content_type(block.hdr));
+ break;
+ case _HDR_CONTENT_DISPOSITION:
+ body_part->content_disposition =
+ p_strdup(pool, _parse_content_disposition(block.hdr));
+ break;
+ default:
+ i_unreached();
+ }
+ } T_END;
+
+ continue;
+ }
+
+ /* Reading body */
+ if ( save_body ) {
+ (void)message_decoder_decode_next_block
+ (decoder, &block, &decoded);
+ buffer_append(buf, decoded.data, decoded.size);
+ }
+ }
+
+ /* even with an empty message there was at least the "end of headers"
+ block, which set the body_part. */
+ i_assert( body_part != NULL );
+
+ /* Save last body part if necessary */
+ if ( header_part != NULL ) {
+ sieve_message_part_save
+ (renv, buf, header_part, FALSE);
+ } else if ( save_body ) {
+ sieve_message_part_save
+ (renv, buf, body_part, extract_text);
+ }
+ if ( iter_all && !array_is_created(&body_part->headers) &&
+ array_count(&headers) > 0 ) {
+ p_array_init(&body_part->headers, pool, array_count(&headers));
+ array_copy(&body_part->headers.arr, 0,
+ &headers.arr, 0, array_count(&headers));
+ }
+
+ /* Try to fill the return_body_parts array once more */
+ have_all = iter_all || sieve_message_body_get_return_parts
+ (renv, content_types, extract_text);
+
+ /* This time, failure is a bug */
+ i_assert(have_all);
+
+ /* Cleanup */
+ (void)message_parser_deinit(&parser, &mparts);
+ message_decoder_deinit(&decoder);
+ buffer_free(&buf);
+
+ /* Return status */
+ if ( input->stream_errno != 0 ) {
+ sieve_runtime_critical(renv, NULL,
+ "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_message_body_get_content
+(const struct sieve_runtime_env *renv,
+ const char * const *content_types,
+ struct sieve_message_part_data **parts_r)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ int status;
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, content_types, FALSE, FALSE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return status;
+}
+
+int sieve_message_body_get_text
+(const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r)
+{
+ static const char * const _text_content_types[] =
+ { "application/xhtml+xml", "text", NULL };
+ struct sieve_message_context *msgctx = renv->msgctx;
+ int status;
+
+ /* We currently only support extracting plain text from:
+
+ - text/html -> HTML
+ - application/xhtml+xml -> XHTML
+
+ Other text types are read as is. Any non-text types are skipped.
+ */
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, _text_content_types, TRUE, FALSE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return status;
+}
+
+int sieve_message_body_get_raw
+(const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part_data *return_part;
+ buffer_t *buf;
+
+ if ( msgctx->raw_body == NULL ) {
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ struct istream *input;
+ struct message_size hdr_size, body_size;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ msgctx->raw_body = buf = buffer_create_dynamic
+ (msgctx->context_pool, 1024*64);
+
+ /* Get stream for message */
+ if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 ) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to open input message");
+ }
+
+ /* Skip stream to beginning of body */
+ i_stream_skip(input, hdr_size.physical_size);
+
+ /* Read raw message body */
+ while ( (ret=i_stream_read_more(input, &data, &size)) > 0 ) {
+ buffer_append(buf, data, size);
+
+ i_stream_skip(input, size);
+ }
+
+ if ( ret < 0 && input->stream_errno != 0 ) {
+ sieve_runtime_critical(renv, NULL,
+ "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+
+ /* Add terminating NUL to the body part buffer */
+ buffer_append_c(buf, '\0');
+
+ } else {
+ buf = msgctx->raw_body;
+ }
+
+ /* Clear result array */
+ array_clear(&msgctx->return_body_parts);
+
+ if ( buf->used > 1 ) {
+ const char *data = (const char *)buf->data;
+ size_t size = buf->used - 1;
+
+ i_assert( data[size] == '\0' );
+
+ /* Add single item to the result */
+ return_part = array_append_space(&msgctx->return_body_parts);
+ return_part->content = data;
+ return_part->size = size;
+ }
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part iterator
+ */
+
+int sieve_message_part_iter_init
+(struct sieve_message_part_iter *iter,
+ const struct sieve_runtime_env *renv)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+ int status;
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, NULL, TRUE, TRUE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ i_zero(iter);
+ iter->renv = renv;
+ iter->index = 0;
+ iter->offset = 0;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if (count == 0)
+ iter->root = NULL;
+ else
+ iter->root = parts[0];
+
+ return SIEVE_EXEC_OK;
+}
+
+void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *subtree)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ *subtree = *iter;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( subtree->index >= count)
+ subtree->root = NULL;
+ else
+ subtree->root = parts[subtree->index];
+ subtree->offset = subtree->index;
+}
+
+void sieve_message_part_iter_children(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *child)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ *child = *iter;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( (child->index+1) >= count || parts[child->index]->children == NULL)
+ child->root = NULL;
+ else
+ child->root = parts[child->index++];
+ child->offset = child->index;
+}
+
+struct sieve_message_part *sieve_message_part_iter_current
+(struct sieve_message_part_iter *iter)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ if ( iter->root == NULL )
+ return NULL;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( iter->index >= count )
+ return NULL;
+ do {
+ if ( parts[iter->index] == iter->root->next )
+ return NULL;
+ if ( parts[iter->index] == iter->root->parent )
+ return NULL;
+ } while ( parts[iter->index]->epilogue && ++iter->index < count );
+ if ( iter->index >= count )
+ return NULL;
+ return parts[iter->index];
+}
+
+struct sieve_message_part *sieve_message_part_iter_next
+(struct sieve_message_part_iter *iter)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+
+ if ( iter->index >= array_count(&msgctx->cached_body_parts) )
+ return NULL;
+ iter->index++;
+
+ return sieve_message_part_iter_current(iter);
+}
+
+void sieve_message_part_iter_reset
+(struct sieve_message_part_iter *iter)
+{
+ iter->index = iter->offset;
+}
+
+/*
+ * MIME header list
+ */
+
+/* Forward declarations */
+
+static int sieve_mime_header_list_next_item
+ (struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r);
+static int sieve_mime_header_list_next_value
+ (struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_mime_header_list_reset
+ (struct sieve_stringlist *_strlist);
+
+/* Header list object */
+
+struct sieve_mime_header_list {
+ struct sieve_header_list hdrlist;
+
+ struct sieve_stringlist *field_names;
+
+ struct sieve_message_part_iter part_iter;
+
+ const char *header_name;
+ const struct sieve_message_header *headers;
+ unsigned int headers_index, headers_count;
+
+ bool mime_decode:1;
+ bool children:1;
+};
+
+struct sieve_header_list *sieve_mime_header_list_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ struct sieve_message_part_iter *part_iter,
+ bool mime_decode, bool children)
+{
+ struct sieve_mime_header_list *hdrlist;
+
+ hdrlist = t_new(struct sieve_mime_header_list, 1);
+ hdrlist->hdrlist.strlist.runenv = renv;
+ hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ hdrlist->hdrlist.strlist.next_item = sieve_mime_header_list_next_value;
+ hdrlist->hdrlist.strlist.reset = sieve_mime_header_list_reset;
+ hdrlist->hdrlist.next_item = sieve_mime_header_list_next_item;
+ hdrlist->field_names = field_names;
+ hdrlist->mime_decode = mime_decode;
+ hdrlist->children = children;
+
+ sieve_message_part_iter_subtree(part_iter, &hdrlist->part_iter);
+
+ return &hdrlist->hdrlist;
+}
+
+/* MIME list implementation */
+
+static void sieve_mime_header_list_next_name
+(struct sieve_mime_header_list *hdrlist)
+{
+ struct sieve_message_part *mpart;
+
+ sieve_message_part_iter_reset(&hdrlist->part_iter);
+ mpart = sieve_message_part_iter_current(&hdrlist->part_iter);
+
+ if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+ hdrlist->headers = array_get
+ (&mpart->headers, &hdrlist->headers_count);
+ hdrlist->headers_index = 0;
+ }
+}
+
+static int sieve_mime_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r)
+{
+ struct sieve_mime_header_list *hdrlist =
+ (struct sieve_mime_header_list *) _hdrlist;
+ const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+
+ if ( name_r != NULL )
+ *name_r = NULL;
+ *value_r = NULL;
+
+ for (;;) {
+ /* Check for end of current header list */
+ if ( hdrlist->headers_count == 0 ||
+ hdrlist->headers_index >= hdrlist->headers_count ) {
+ hdrlist->headers_count = 0;
+ hdrlist->headers_index = 0;
+ hdrlist->headers = NULL;
+ }
+
+ /* Fetch more headers */
+ while ( hdrlist->headers_count == 0 ) {
+ string_t *hdr_item = NULL;
+ int ret;
+
+ if ( hdrlist->header_name != NULL && hdrlist->children ) {
+ struct sieve_message_part *mpart;
+
+ mpart = sieve_message_part_iter_next(&hdrlist->part_iter);
+ if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+ hdrlist->headers = array_get
+ (&mpart->headers, &hdrlist->headers_count);
+ hdrlist->headers_index = 0;
+ }
+ if ( hdrlist->headers_count > 0 ) {
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "moving to next message part");
+ }
+ break;
+ }
+ }
+
+ /* Read next header name from source list */
+ if ( (ret=sieve_stringlist_next_item
+ (hdrlist->field_names, &hdr_item)) <= 0 )
+ return ret;
+
+ hdrlist->header_name = str_c(hdr_item);
+
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracting `%s' headers from message part",
+ str_sanitize(str_c(hdr_item), 80));
+ }
+
+ sieve_mime_header_list_next_name(hdrlist);
+ }
+
+ for ( ; hdrlist->headers_index < hdrlist->headers_count;
+ hdrlist->headers_index++ ) {
+ const struct sieve_message_header *header =
+ &hdrlist->headers[hdrlist->headers_index];
+
+ if ( strcasecmp(header->name, hdrlist->header_name) == 0 ) {
+ if ( name_r != NULL )
+ *name_r = hdrlist->header_name;
+ if ( hdrlist->mime_decode ) {
+ *value_r = t_str_new_const
+ ((const char *)header->utf8_value, header->utf8_value_len);
+ } else {
+ *value_r = t_str_new_const
+ ((const char *)header->value, header->value_len);
+ }
+ hdrlist->headers_index++;
+ return 1;
+ }
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+static int sieve_mime_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+ struct sieve_header_list *hdrlist =
+ (struct sieve_header_list *) _strlist;
+
+ return sieve_mime_header_list_next_item
+ (hdrlist, NULL, value_r);
+}
+
+static void sieve_mime_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+ struct sieve_mime_header_list *hdrlist =
+ (struct sieve_mime_header_list *) strlist;
+
+ sieve_stringlist_reset(hdrlist->field_names);
+ hdrlist->header_name = NULL;
+}