diff --git a/Makefile.am b/Makefile.am index 43a7b56dc87cc9e699adc94329cbbacfe49adda4..c85dd942cd96aea31c7bfab98a691030847eeff8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ test_cases = \ tests/extensions/imap4flags/execute.svtest \ tests/extensions/imap4flags/flagstore.svtest \ tests/extensions/body/basic.svtest \ + tests/extensions/body/raw.svtest \ tests/extensions/body/match-values.svtest \ tests/extensions/regex/basic.svtest \ tests/extensions/regex/match-values.svtest \ diff --git a/src/lib-sieve/plugins/body/ext-body-common.c b/src/lib-sieve/plugins/body/ext-body-common.c index 1d92817534ad4929d19c9cb30c9f8898bc357861..45767de5ce89153589c9a2df41a1a3e60a0d0d0d 100644 --- a/src/lib-sieve/plugins/body/ext-body-common.c +++ b/src/lib-sieve/plugins/body/ext-body-common.c @@ -38,6 +38,7 @@ struct ext_body_message_context { ARRAY_DEFINE(cached_body_parts, struct ext_body_part_cached); ARRAY_DEFINE(return_body_parts, struct ext_body_part); buffer_t *tmp_buffer; + buffer_t *raw_body; }; static bool _is_wanted_content_type @@ -310,7 +311,8 @@ static struct ext_body_message_context *ext_body_get_context p_array_init(&ctx->cached_body_parts, pool, 8); p_array_init(&ctx->return_body_parts, pool, 8); ctx->tmp_buffer = buffer_create_dynamic(pool, 1024*64); - + ctx->raw_body = NULL; + /* Register context */ sieve_message_context_extension_set(msgctx, &body_extension, (void *) ctx); } @@ -341,3 +343,57 @@ bool ext_body_get_content return result; } + +bool ext_body_get_raw +(const struct sieve_runtime_env *renv, struct ext_body_part **parts_r) +{ + struct ext_body_message_context *ctx = ext_body_get_context(renv->msgctx); + struct ext_body_part *return_part; + buffer_t *buf; + + if ( ctx->raw_body == NULL ) { + struct mail *mail = renv->msgdata->mail; + struct istream *input; + struct message_size hdr_size, body_size; + const unsigned char *data; + size_t size; + int ret; + + ctx->raw_body = buf = buffer_create_dynamic(ctx->pool, 1024*64); + + /* Get stream for message */ + if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 ) + return FALSE; + + /* Skip stream to beginning of body */ + i_stream_skip(input, hdr_size.physical_size); + + /* Read raw message body */ + while ( (ret = i_stream_read_data(input, &data, &size, 0)) > 0 ) { + buffer_append(buf, data, size); + + i_stream_skip(input, size); + } + } else { + buf = ctx->raw_body; + } + + /* Clear result array */ + array_clear(&ctx->return_body_parts); + + if ( buf->used > 0 ) { + /* Add terminating NUL to the body part buffer */ + buffer_append_c(buf, '\0'); + + /* Add single item to the result */ + return_part = array_append_space(&ctx->return_body_parts); + return_part->content = buf->data; + return_part->size = buf->used - 1; + } + + /* Return the array of body items */ + (void) array_append_space(&ctx->return_body_parts); /* NULL-terminate */ + *parts_r = array_idx_modifiable(&ctx->return_body_parts, 0); + + return TRUE; +} diff --git a/src/lib-sieve/plugins/body/ext-body-common.h b/src/lib-sieve/plugins/body/ext-body-common.h index edb8fe96f6695373b87f42645a2866951f83b6d0..33a0f6c8cb6c4386e772748624e0e07827f0dc84 100644 --- a/src/lib-sieve/plugins/body/ext-body-common.h +++ b/src/lib-sieve/plugins/body/ext-body-common.h @@ -32,7 +32,10 @@ struct ext_body_part { }; bool ext_body_get_content -(const struct sieve_runtime_env *renv, const char * const *content_types, - int decode_to_plain, struct ext_body_part **parts_r); + (const struct sieve_runtime_env *renv, const char * const *content_types, + int decode_to_plain, struct ext_body_part **parts_r); + +bool ext_body_get_raw + (const struct sieve_runtime_env *renv, struct ext_body_part **parts_r); #endif /* __EXT_BODY_COMMON_H */ diff --git a/src/lib-sieve/plugins/body/tst-body.c b/src/lib-sieve/plugins/body/tst-body.c index a775e29fc34026ad2ac9f8f8ce9b75214473dbc8..c1057cc21db29608bbee4587c0b1ad54a310a0ca 100644 --- a/src/lib-sieve/plugins/body/tst-body.c +++ b/src/lib-sieve/plugins/body/tst-body.c @@ -380,9 +380,15 @@ static int ext_body_operation_execute /* Extract requested parts */ - if ( !ext_body_get_content - (renv, content_types, transform != TST_BODY_TRANSFORM_RAW, &body_parts) ) { - return SIEVE_EXEC_FAILURE; + if ( transform == TST_BODY_TRANSFORM_RAW ) { + if ( !ext_body_get_raw(renv, &body_parts) ) { + return SIEVE_EXEC_FAILURE; + } + } else { + if ( !ext_body_get_content + (renv, content_types, TRUE, &body_parts) ) { + return SIEVE_EXEC_FAILURE; + } } /* Disable match values processing as required by RFC */ diff --git a/tests/extensions/body/basic.svtest b/tests/extensions/body/basic.svtest index 042b88a9671a6a024012098fc55a1a957d86f406..7f886435fd3c6d023724f1f0efbfdab888b3f068 100644 --- a/tests/extensions/body/basic.svtest +++ b/tests/extensions/body/basic.svtest @@ -29,7 +29,7 @@ Test! . { - test_fail "invalid message body extracted"; + test_fail "invalid message body extracted (1)"; } if body :raw :is text: @@ -38,12 +38,12 @@ Test! . { - test_fail "invalid message body extracted"; + test_fail "invalid message body extracted (2)"; } if body :raw :is "Test" { - test_fail "body test matches nonsense"; + test_fail "body test matches nonsense (3)"; } } diff --git a/tests/extensions/body/raw.svtest b/tests/extensions/body/raw.svtest new file mode 100644 index 0000000000000000000000000000000000000000..ba6696eac67a1070ebb30a4776509865f82b34e3 --- /dev/null +++ b/tests/extensions/body/raw.svtest @@ -0,0 +1,85 @@ +require "vnd.dovecot.testsuite"; +require "body"; + +test_set "message" text: +From: Whomever <whoever@example.com> +To: Someone <someone@example.com> +Date: Sat, 10 Oct 2009 00:30:04 +0200 +Subject: whatever +Content-Type: multipart/mixed; boundary=outer + +This is a multi-part message in MIME format. + +--outer +Content-Type: multipart/alternative; boundary=inner + +This is a nested multi-part message in MIME format. + +--inner +Content-Type: text/plain; charset="us-ascii" + +Hello + +--inner +Content-Type: text/html; charset="us-ascii" + +<html><body>Hello</body></html> + +--inner-- + +This is the end of the inner MIME multipart. + +--outer +Content-Type: message/rfc822 + +From: Someone Else +Subject: hello request + +Please say Hello + +--outer-- + +This is the end of the outer MIME multipart. +. +; + +/* + * + * RFC 5173: + * The ":raw" transform matches against the entire undecoded body of a + * message as a single item. + * + * If the specified body-transform is ":raw", the [MIME] structure of + * the body is irrelevant. The implementation MUST NOT remove any + * transfer encoding from the message, MUST NOT refuse to filter + * messages with syntactic errors (unless the environment it is part of + * rejects them outright), and MUST treat multipart boundaries or the + * MIME headers of enclosed body parts as part of the content being + * matched against, instead of MIME structures to interpret. + */ + +test "Multipart Boundaries" { + if not body :raw :contains "--inner" { + test_fail "Raw body does not contain '--inner'"; + } + + if not body :raw :contains "--outer" { + test_fail "Raw body does not contain '--outer'"; + } +} + +test "Multipart Headers" { + if not body :raw :contains "boundary=inner" { + test_fail "Raw body does not contain 'boundary=inner'"; + } + + if not body :raw :contains "rfc822" { + test_fail "Raw body does not contain 'rfc822'"; + } +} + +test "Multipart Content" { + if not body :raw :contains "<html><body>Hello</body></html>" { + test_fail "Raw body does not contain '<html><body>Hello</body></html>'"; + } +}