diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-message.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-message.c | 1845 |
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; +} |