1
/* Copyright (c) 2002-2009 Dovecot Sieve authors, see the included COPYING file
10
#include "rfc822-parser.h"
11
#include "message-date.h"
12
#include "message-parser.h"
13
#include "message-decoder.h"
15
#include "sieve-common.h"
16
#include "sieve-message.h"
17
#include "sieve-interpreter.h"
19
#include "ext-body-common.h"
21
/* This implementation is largely borrowed from the original sieve-cmu.c of the
25
struct ext_body_part_cached {
26
const char *content_type;
29
const char *decoded_body;
31
size_t decoded_body_size;
33
bool have_body; /* there's the empty end-of-headers line */
36
struct ext_body_message_context {
38
ARRAY_DEFINE(cached_body_parts, struct ext_body_part_cached);
39
ARRAY_DEFINE(return_body_parts, struct ext_body_part);
44
static bool _is_wanted_content_type
45
(const char * const *wanted_types, const char *content_type)
47
const char *subtype = strchr(content_type, '/');
50
type_len = ( subtype == NULL ? strlen(content_type) :
51
(size_t)(subtype - content_type) );
53
i_assert( wanted_types != NULL );
55
for (; *wanted_types != NULL; wanted_types++) {
56
const char *wanted_subtype = strchr(*wanted_types, '/');
58
if (**wanted_types == '\0') {
59
/* empty string matches everything */
62
if (wanted_subtype == NULL) {
63
/* match only main type */
64
if (strlen(*wanted_types) == type_len &&
65
strncasecmp(*wanted_types, content_type, type_len) == 0)
68
/* match whole type/subtype */
69
if (strcasecmp(*wanted_types, content_type) == 0)
76
static bool ext_body_get_return_parts
77
(struct ext_body_message_context *ctx, const char * const *wanted_types,
80
const struct ext_body_part_cached *body_parts;
81
unsigned int i, count;
82
struct ext_body_part *return_part;
84
/* Check whether any body parts are cached already */
85
body_parts = array_get(&ctx->cached_body_parts, &count);
89
/* Clear result array */
90
array_clear(&ctx->return_body_parts);
92
/* Fill result array with requested content_types */
93
for (i = 0; i < count; i++) {
94
if (!body_parts[i].have_body) {
95
/* Part has no body; according to RFC this MUST not match to anything and
96
* therefore it is not included in the result.
101
/* Skip content types that are not requested */
102
if (!_is_wanted_content_type(wanted_types, body_parts[i].content_type))
105
/* Add new item to the result */
106
return_part = array_append_space(&ctx->return_body_parts);
108
/* Depending on whether a decoded body part is requested, the appropriate
109
* cache item is read. If it is missing, this function fails and the cache
110
* needs to be completed by ext_body_parts_add_missing().
112
if (decode_to_plain) {
113
if (body_parts[i].decoded_body == NULL)
115
return_part->content = body_parts[i].decoded_body;
116
return_part->size = body_parts[i].decoded_body_size;
118
if (body_parts[i].raw_body == NULL)
120
return_part->content = body_parts[i].raw_body;
121
return_part->size = body_parts[i].raw_body_size;
128
static void ext_body_part_save
129
(struct ext_body_message_context *ctx, struct message_part *part,
130
struct ext_body_part_cached *body_part, bool decoded)
132
buffer_t *buf = ctx->tmp_buffer;
136
/* Add terminating NUL to the body part buffer */
137
buffer_append_c(buf, '\0');
139
part_data = p_malloc(ctx->pool, buf->used);
140
memcpy(part_data, buf->data, buf->used);
141
part_size = buf->used - 1;
143
/* Depending on whether the part is decoded or not store message body in the
144
* appropriate cache location.
147
body_part->raw_body = part_data;
148
body_part->raw_body_size = part_size;
149
i_assert(buf->used - 1 == part->body_size.physical_size);
151
body_part->decoded_body = part_data;
152
body_part->decoded_body_size = part_size;
156
buffer_set_used_size(buf, 0);
159
static const char *_parse_content_type(const struct message_header_line *hdr)
161
struct rfc822_parser_context parser;
162
string_t *content_type;
164
/* Initialize parsing */
165
rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
166
(void)rfc822_skip_lwsp(&parser);
168
/* Parse content type */
169
content_type = t_str_new(64);
170
if (rfc822_parse_content_type(&parser, content_type) < 0)
173
/* Content-type value must end here, otherwise it is invalid after all */
174
(void)rfc822_skip_lwsp(&parser);
175
if ( parser.data != parser.end && *parser.data != ';' )
179
return str_c(content_type);
182
/* ext_body_parts_add_missing():
183
* Add requested message body parts to the cache that are missing.
185
static bool ext_body_parts_add_missing
186
(const struct sieve_message_data *msgdata, struct ext_body_message_context *ctx,
187
const char * const *content_types, bool decode_to_plain)
189
struct ext_body_part_cached *body_part = NULL;
190
struct message_parser_ctx *parser;
191
struct message_decoder_context *decoder;
192
struct message_block block, decoded;
193
struct message_part *parts, *prev_part = NULL;
194
struct istream *input;
195
unsigned int idx = 0;
196
bool save_body = FALSE, have_all;
199
/* First check whether any are missing */
200
if (ext_body_get_return_parts(ctx, content_types, decode_to_plain)) {
201
/* Cache hit; all are present */
205
/* Get the message stream */
206
if ( mail_get_stream(msgdata->mail, NULL, NULL, &input) < 0 )
209
buffer_set_used_size(ctx->tmp_buffer, 0);
211
/* Initialize body decoder */
212
decoder = decode_to_plain ? message_decoder_init(FALSE) : NULL;
214
parser = message_parser_init(ctx->pool, input, 0, 0);
215
while ( (ret = message_parser_parse_next_block(parser, &block)) > 0 ) {
216
if ( block.part != prev_part ) {
217
/* Save previous body part */
218
if ( body_part != NULL && save_body ) {
219
ext_body_part_save(ctx, prev_part, body_part, decoder != NULL);
222
/* Start processing next */
223
prev_part = block.part;
224
body_part = array_idx_modifiable(&ctx->cached_body_parts, idx);
226
body_part->content_type = "text/plain";
229
if ( block.hdr != NULL || block.size == 0 ) {
230
/* Reading headers */
233
if ( decoder != NULL )
234
(void)message_decoder_decode_next_block(decoder, &block, &decoded);
236
/* Check for end of headers */
237
if ( block.hdr == NULL ) {
238
/* Save bodies only if we have a wanted content-type */
239
save_body = _is_wanted_content_type
240
(content_types, body_part->content_type);
244
/* Encountered the empty line that indicates the end of the headers and
245
* the start of the body
247
if ( block.hdr->eoh )
248
body_part->have_body = TRUE;
250
/* We're interested of only Content-Type: header */
251
if ( strcasecmp(block.hdr->name, "Content-Type" ) != 0)
254
/* Header can have folding whitespace. Acquire the full value before
257
if ( block.hdr->continues ) {
258
block.hdr->use_full_value = TRUE;
262
/* Parse the content type from the Content-type header */
264
body_part->content_type =
265
p_strdup(ctx->pool, _parse_content_type(block.hdr));
273
if ( decoder != NULL ) {
274
(void)message_decoder_decode_next_block(decoder, &block, &decoded);
275
buffer_append(ctx->tmp_buffer, decoded.data, decoded.size);
277
buffer_append(ctx->tmp_buffer, block.data, block.size);
282
/* Save last body part if necessary */
283
if ( body_part != NULL && save_body )
284
ext_body_part_save(ctx, prev_part, body_part, decoder != NULL);
286
/* Try to fill the return_body_parts array once more */
287
have_all = ext_body_get_return_parts(ctx, content_types, decode_to_plain);
289
/* This time, failure is a bug */
293
(void)message_parser_deinit(&parser, &parts);
295
message_decoder_deinit(&decoder);
298
return ( input->stream_errno == 0 );
301
static struct ext_body_message_context *ext_body_get_context
302
(struct sieve_message_context *msgctx)
304
pool_t pool = sieve_message_context_pool(msgctx);
305
struct ext_body_message_context *ctx;
307
/* Get message context (contains cached message body information) */
308
ctx = (struct ext_body_message_context *)
309
sieve_message_context_extension_get(msgctx, &body_extension);
311
/* Create it if it does not exist already */
313
ctx = p_new(pool, struct ext_body_message_context, 1);
315
p_array_init(&ctx->cached_body_parts, pool, 8);
316
p_array_init(&ctx->return_body_parts, pool, 8);
317
ctx->tmp_buffer = buffer_create_dynamic(pool, 1024*64);
318
ctx->raw_body = NULL;
320
/* Register context */
321
sieve_message_context_extension_set(msgctx, &body_extension, (void *) ctx);
327
bool ext_body_get_content
328
(const struct sieve_runtime_env *renv, const char * const *content_types,
329
int decode_to_plain, struct ext_body_part **parts_r)
332
struct ext_body_message_context *ctx = ext_body_get_context(renv->msgctx);
335
/* Fill the return_body_parts array */
336
if ( !ext_body_parts_add_missing
337
(renv->msgdata, ctx, content_types, decode_to_plain != 0) )
342
if ( !result ) return FALSE;
344
/* Return the array of body items */
345
(void) array_append_space(&ctx->return_body_parts); /* NULL-terminate */
346
*parts_r = array_idx_modifiable(&ctx->return_body_parts, 0);
351
bool ext_body_get_raw
352
(const struct sieve_runtime_env *renv, struct ext_body_part **parts_r)
354
struct ext_body_message_context *ctx = ext_body_get_context(renv->msgctx);
355
struct ext_body_part *return_part;
358
if ( ctx->raw_body == NULL ) {
359
struct mail *mail = renv->msgdata->mail;
360
struct istream *input;
361
struct message_size hdr_size, body_size;
362
const unsigned char *data;
366
ctx->raw_body = buf = buffer_create_dynamic(ctx->pool, 1024*64);
368
/* Get stream for message */
369
if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 )
372
/* Skip stream to beginning of body */
373
i_stream_skip(input, hdr_size.physical_size);
375
/* Read raw message body */
376
while ( (ret = i_stream_read_data(input, &data, &size, 0)) > 0 ) {
377
buffer_append(buf, data, size);
379
i_stream_skip(input, size);
385
/* Clear result array */
386
array_clear(&ctx->return_body_parts);
388
if ( buf->used > 0 ) {
389
/* Add terminating NUL to the body part buffer */
390
buffer_append_c(buf, '\0');
392
/* Add single item to the result */
393
return_part = array_append_space(&ctx->return_body_parts);
394
return_part->content = buf->data;
395
return_part->size = buf->used - 1;
398
/* Return the array of body items */
399
(void) array_append_space(&ctx->return_body_parts); /* NULL-terminate */
400
*parts_r = array_idx_modifiable(&ctx->return_body_parts, 0);