~ubuntu-branches/ubuntu/wily/dovecot/wily

« back to all changes in this revision

Viewing changes to src/lib-mail/istream-attachment-extractor.c

  • Committer: Package Import Robot
  • Author(s): Jaldhar H. Vyas
  • Date: 2013-09-09 00:57:32 UTC
  • mfrom: (1.13.11)
  • mto: (4.8.5 experimental) (1.16.1)
  • mto: This revision was merged to the branch mainline in revision 97.
  • Revision ID: package-import@ubuntu.com-20130909005732-dn1eell8srqbhh0e
Tags: upstream-2.2.5
ImportĀ upstreamĀ versionĀ 2.2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
 
2
 
 
3
#include "lib.h"
 
4
#include "istream-private.h"
 
5
#include "ostream.h"
 
6
#include "base64.h"
 
7
#include "buffer.h"
 
8
#include "str.h"
 
9
#include "hash-format.h"
 
10
#include "rfc822-parser.h"
 
11
#include "message-parser.h"
 
12
#include "istream-attachment-extractor.h"
 
13
 
 
14
#define BASE64_ATTACHMENT_MAX_EXTRA_BYTES 1024
 
15
 
 
16
enum mail_attachment_state {
 
17
        MAIL_ATTACHMENT_STATE_NO,
 
18
        MAIL_ATTACHMENT_STATE_MAYBE,
 
19
        MAIL_ATTACHMENT_STATE_YES
 
20
};
 
21
 
 
22
enum base64_state {
 
23
        BASE64_STATE_0 = 0,
 
24
        BASE64_STATE_1,
 
25
        BASE64_STATE_2,
 
26
        BASE64_STATE_3,
 
27
        BASE64_STATE_CR,
 
28
        BASE64_STATE_EOB,
 
29
        BASE64_STATE_EOM
 
30
};
 
31
 
 
32
struct attachment_istream_part {
 
33
        char *content_type, *content_disposition;
 
34
        enum mail_attachment_state state;
 
35
        /* start offset of the message part in the original input stream */
 
36
        uoff_t start_offset;
 
37
 
 
38
        /* for saving attachments base64-decoded: */
 
39
        enum base64_state base64_state;
 
40
        unsigned int base64_line_blocks, cur_base64_blocks;
 
41
        uoff_t base64_bytes;
 
42
        bool base64_have_crlf; /* CRLF linefeeds */
 
43
        bool base64_failed;
 
44
 
 
45
        int temp_fd;
 
46
        struct ostream *temp_output;
 
47
        buffer_t *part_buf;
 
48
};
 
49
 
 
50
struct attachment_istream {
 
51
        struct istream_private istream;
 
52
        pool_t pool;
 
53
 
 
54
        struct istream_attachment_settings set;
 
55
        void *context;
 
56
 
 
57
        struct message_parser_ctx *parser;
 
58
        struct message_part *cur_part;
 
59
        struct attachment_istream_part part;
 
60
 
 
61
        bool retry_read;
 
62
};
 
63
 
 
64
static void stream_add_data(struct attachment_istream *astream,
 
65
                            const void *data, size_t size)
 
66
{
 
67
        if (size > 0) {
 
68
                memcpy(i_stream_alloc(&astream->istream, size), data, size);
 
69
                astream->istream.pos += size;
 
70
        }
 
71
}
 
72
 
 
73
static void parse_content_type(struct attachment_istream *astream,
 
74
                               const struct message_header_line *hdr)
 
75
{
 
76
        struct rfc822_parser_context parser;
 
77
        string_t *content_type;
 
78
 
 
79
        rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
 
80
        rfc822_skip_lwsp(&parser);
 
81
 
 
82
        T_BEGIN {
 
83
                content_type = t_str_new(64);
 
84
                if (rfc822_parse_content_type(&parser, content_type) >= 0) {
 
85
                        i_free(astream->part.content_type);
 
86
                        astream->part.content_type =
 
87
                                i_strdup(str_c(content_type));
 
88
                }
 
89
        } T_END;
 
90
}
 
91
 
 
92
static void
 
93
parse_content_disposition(struct attachment_istream *astream,
 
94
                          const struct message_header_line *hdr)
 
95
{
 
96
        /* just pass it without parsing to is_attachment() callback */
 
97
        i_free(astream->part.content_disposition);
 
98
        astream->part.content_disposition =
 
99
                i_strndup(hdr->full_value, hdr->full_value_len);
 
100
}
 
101
 
 
102
static void astream_parse_header(struct attachment_istream *astream,
 
103
                                 struct message_header_line *hdr)
 
104
{
 
105
        if (!hdr->continued) {
 
106
                stream_add_data(astream, hdr->name, hdr->name_len);
 
107
                stream_add_data(astream, hdr->middle, hdr->middle_len);
 
108
        }
 
109
        stream_add_data(astream, hdr->value, hdr->value_len);
 
110
        if (!hdr->no_newline) {
 
111
                if (hdr->crlf_newline)
 
112
                        stream_add_data(astream, "\r\n", 2);
 
113
                else
 
114
                        stream_add_data(astream, "\n", 1);
 
115
        }
 
116
 
 
117
        if (hdr->continues) {
 
118
                hdr->use_full_value = TRUE;
 
119
                return;
 
120
        }
 
121
 
 
122
        if (strcasecmp(hdr->name, "Content-Type") == 0)
 
123
                parse_content_type(astream, hdr);
 
124
        else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
 
125
                parse_content_disposition(astream, hdr);
 
126
}
 
127
 
 
128
static bool astream_want_attachment(struct attachment_istream *astream,
 
129
                                    struct message_part *part)
 
130
{
 
131
        struct istream_attachment_header ahdr;
 
132
 
 
133
        if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
 
134
                /* multiparts may contain attachments as children,
 
135
                   but they're never themselves */
 
136
                return FALSE;
 
137
        }
 
138
        if (astream->set.want_attachment == NULL)
 
139
                return TRUE;
 
140
 
 
141
        memset(&ahdr, 0, sizeof(ahdr));
 
142
        ahdr.part = part;
 
143
        ahdr.content_type = astream->part.content_type;
 
144
        ahdr.content_disposition = astream->part.content_disposition;
 
145
        return astream->set.want_attachment(&ahdr, astream->context);
 
146
}
 
147
 
 
148
static int astream_base64_decode_lf(struct attachment_istream_part *part)
 
149
{
 
150
        part->base64_state = BASE64_STATE_0;
 
151
        if (part->cur_base64_blocks < part->base64_line_blocks) {
 
152
                /* last line */
 
153
                part->base64_state = BASE64_STATE_EOM;
 
154
                return 0;
 
155
        } else if (part->base64_line_blocks == 0) {
 
156
                /* first line */
 
157
                if (part->cur_base64_blocks == 0)
 
158
                        return -1;
 
159
                part->base64_line_blocks = part->cur_base64_blocks;
 
160
        } else if (part->cur_base64_blocks == part->base64_line_blocks) {
 
161
                /* line is ok */
 
162
        } else {
 
163
                return -1;
 
164
        }
 
165
        part->cur_base64_blocks = 0;
 
166
        return 1;
 
167
}
 
168
 
 
169
static int
 
170
astream_try_base64_decode_char(struct attachment_istream_part *part,
 
171
                               size_t pos, char chr)
 
172
{
 
173
        switch (part->base64_state) {
 
174
        case BASE64_STATE_0:
 
175
                if (base64_is_valid_char(chr))
 
176
                        part->base64_state++;
 
177
                else if (chr == '\r')
 
178
                        part->base64_state = BASE64_STATE_CR;
 
179
                else if (chr == '\n') {
 
180
                        return astream_base64_decode_lf(part);
 
181
                } else {
 
182
                        return -1;
 
183
                }
 
184
                break;
 
185
        case BASE64_STATE_1:
 
186
                if (!base64_is_valid_char(chr))
 
187
                        return -1;
 
188
                part->base64_state++;
 
189
                break;
 
190
        case BASE64_STATE_2:
 
191
                if (base64_is_valid_char(chr))
 
192
                        part->base64_state++;
 
193
                else if (chr == '=')
 
194
                        part->base64_state = BASE64_STATE_EOB;
 
195
                else
 
196
                        return -1;
 
197
                break;
 
198
        case BASE64_STATE_3:
 
199
                part->base64_bytes = part->temp_output->offset + pos + 1;
 
200
                if (base64_is_valid_char(chr)) {
 
201
                        part->base64_state = BASE64_STATE_0;
 
202
                        part->cur_base64_blocks++;
 
203
                } else if (chr == '=') {
 
204
                        part->base64_state = BASE64_STATE_EOM;
 
205
                        part->cur_base64_blocks++;
 
206
                        return 0;
 
207
                } else {
 
208
                        return -1;
 
209
                }
 
210
                break;
 
211
        case BASE64_STATE_CR:
 
212
                if (chr != '\n')
 
213
                        return -1;
 
214
                part->base64_have_crlf = TRUE;
 
215
                return astream_base64_decode_lf(part);
 
216
        case BASE64_STATE_EOB:
 
217
                if (chr != '=')
 
218
                        return -1;
 
219
 
 
220
                part->base64_bytes = part->temp_output->offset + pos + 1;
 
221
                part->base64_state = BASE64_STATE_EOM;
 
222
                part->cur_base64_blocks++;
 
223
                return 0;
 
224
        case BASE64_STATE_EOM:
 
225
                i_unreached();
 
226
        }
 
227
        return 1;
 
228
}
 
229
 
 
230
static void
 
231
astream_try_base64_decode(struct attachment_istream_part *part,
 
232
                          const unsigned char *data, size_t size)
 
233
{
 
234
        size_t i;
 
235
        int ret;
 
236
 
 
237
        if (part->base64_failed || part->base64_state == BASE64_STATE_EOM)
 
238
                return;
 
239
 
 
240
        for (i = 0; i < size; i++) {
 
241
                ret = astream_try_base64_decode_char(part, i, (char)data[i]);
 
242
                if (ret <= 0) {
 
243
                        if (ret < 0)
 
244
                                part->base64_failed = TRUE;
 
245
                        break;
 
246
                }
 
247
        }
 
248
}
 
249
 
 
250
static int astream_open_output(struct attachment_istream *astream)
 
251
{
 
252
        int fd;
 
253
 
 
254
        i_assert(astream->part.temp_fd == -1);
 
255
 
 
256
        fd = astream->set.open_temp_fd(astream->context);
 
257
        if (fd == -1)
 
258
                return -1;
 
259
 
 
260
        astream->part.temp_fd = fd;
 
261
        astream->part.temp_output = o_stream_create_fd(fd, 0, FALSE);
 
262
        o_stream_cork(astream->part.temp_output);
 
263
        return 0;
 
264
}
 
265
 
 
266
static void astream_add_body(struct attachment_istream *astream,
 
267
                             const struct message_block *block)
 
268
{
 
269
        struct attachment_istream_part *part = &astream->part;
 
270
        buffer_t *part_buf;
 
271
        size_t new_size;
 
272
 
 
273
        switch (part->state) {
 
274
        case MAIL_ATTACHMENT_STATE_NO:
 
275
                stream_add_data(astream, block->data, block->size);
 
276
                break;
 
277
        case MAIL_ATTACHMENT_STATE_MAYBE:
 
278
                /* we'll write data to in-memory buffer until we reach
 
279
                   attachment min_size */
 
280
                if (part->part_buf == NULL) {
 
281
                        part->part_buf =
 
282
                                buffer_create_dynamic(default_pool,
 
283
                                                      astream->set.min_size);
 
284
                }
 
285
                part_buf = part->part_buf;
 
286
                new_size = part_buf->used + block->size;
 
287
                if (new_size < astream->set.min_size) {
 
288
                        buffer_append(part_buf, block->data, block->size);
 
289
                        break;
 
290
                }
 
291
                /* attachment is large enough. we'll first copy the buffered
 
292
                   data from memory to temp file */
 
293
                if (astream_open_output(astream) < 0) {
 
294
                        /* failed, fallback to just saving it inline */
 
295
                        part->state = MAIL_ATTACHMENT_STATE_NO;
 
296
                        stream_add_data(astream, part_buf->data, part_buf->used);
 
297
                        stream_add_data(astream, block->data, block->size);
 
298
                        break;
 
299
                }
 
300
                part->state = MAIL_ATTACHMENT_STATE_YES;
 
301
                astream_try_base64_decode(part, part_buf->data, part_buf->used);
 
302
                hash_format_loop(astream->set.hash_format,
 
303
                                 part_buf->data, part_buf->used);
 
304
                o_stream_nsend(part->temp_output,
 
305
                               part_buf->data, part_buf->used);
 
306
                buffer_set_used_size(part_buf, 0);
 
307
                /* fall through to write the new data to temp file */
 
308
        case MAIL_ATTACHMENT_STATE_YES:
 
309
                astream_try_base64_decode(part, block->data, block->size);
 
310
                hash_format_loop(astream->set.hash_format,
 
311
                                 block->data, block->size);
 
312
                o_stream_nsend(part->temp_output, block->data, block->size);
 
313
                break;
 
314
        }
 
315
}
 
316
 
 
317
static int astream_decode_base64(struct attachment_istream *astream)
 
318
{
 
319
        struct attachment_istream_part *part = &astream->part;
 
320
        buffer_t *extra_buf = NULL;
 
321
        struct istream *input, *base64_input;
 
322
        struct ostream *output;
 
323
        const unsigned char *data;
 
324
        size_t size;
 
325
        ssize_t ret;
 
326
        buffer_t *buf;
 
327
        int outfd;
 
328
        bool failed = FALSE;
 
329
 
 
330
        if (part->base64_bytes < astream->set.min_size ||
 
331
            part->temp_output->offset > part->base64_bytes +
 
332
                                        BASE64_ATTACHMENT_MAX_EXTRA_BYTES) {
 
333
                /* only a small part of the MIME part is base64-encoded. */
 
334
                return -1;
 
335
        }
 
336
 
 
337
        if (part->base64_line_blocks == 0) {
 
338
                /* only one line of base64 */
 
339
                part->base64_line_blocks = part->cur_base64_blocks;
 
340
                i_assert(part->base64_line_blocks > 0);
 
341
        }
 
342
 
 
343
        /* decode base64 data and write it to another temp file */
 
344
        outfd = astream->set.open_temp_fd(astream->context);
 
345
        if (outfd == -1)
 
346
                return -1;
 
347
 
 
348
        buf = buffer_create_dynamic(default_pool, 1024);
 
349
        input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
 
350
        base64_input = i_stream_create_limit(input, part->base64_bytes);
 
351
        output = o_stream_create_fd_file(outfd, 0, FALSE);
 
352
        o_stream_cork(output);
 
353
 
 
354
        hash_format_reset(astream->set.hash_format);
 
355
        while ((ret = i_stream_read(base64_input)) > 0) {
 
356
                data = i_stream_get_data(base64_input, &size);
 
357
                buffer_set_used_size(buf, 0);
 
358
                if (base64_decode(data, size, &size, buf) < 0) {
 
359
                        i_error("istream-attachment: BUG: "
 
360
                                "Attachment base64 data unexpectedly broke");
 
361
                        failed = TRUE;
 
362
                        break;
 
363
                }
 
364
                i_stream_skip(base64_input, size);
 
365
                o_stream_nsend(output, buf->data, buf->used);
 
366
                hash_format_loop(astream->set.hash_format,
 
367
                                 buf->data, buf->used);
 
368
        }
 
369
        if (ret != -1) {
 
370
                i_assert(failed);
 
371
        } else if (base64_input->stream_errno != 0) {
 
372
                i_error("istream-attachment: read(%s) failed: %m",
 
373
                        i_stream_get_name(base64_input));
 
374
                failed = TRUE;
 
375
        }
 
376
        if (o_stream_nfinish(output) < 0) {
 
377
                i_error("istream-attachment: write(%s) failed: %m",
 
378
                        o_stream_get_name(output));
 
379
                failed = TRUE;
 
380
        }
 
381
 
 
382
        buffer_free(&buf);
 
383
        i_stream_unref(&base64_input);
 
384
        o_stream_unref(&output);
 
385
 
 
386
        if (input->v_offset != part->temp_output->offset && !failed) {
 
387
                /* write the rest of the data to the message stream */
 
388
                extra_buf = buffer_create_dynamic(default_pool, 1024);
 
389
                while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) {
 
390
                        buffer_append(extra_buf, data, size);
 
391
                        i_stream_skip(input, size);
 
392
                }
 
393
                i_assert(ret == -1);
 
394
                if (input->stream_errno != 0) {
 
395
                        i_error("istream-attachment: read(%s) failed: %m",
 
396
                                i_stream_get_name(base64_input));
 
397
                        failed = TRUE;
 
398
                }
 
399
        }
 
400
        i_stream_unref(&input);
 
401
 
 
402
        if (failed) {
 
403
                i_close_fd(&outfd);
 
404
                return -1;
 
405
        }
 
406
 
 
407
        /* successfully wrote it. switch to using it. */
 
408
        o_stream_destroy(&part->temp_output);
 
409
        i_close_fd(&part->temp_fd);
 
410
        part->temp_fd = outfd;
 
411
 
 
412
        if (extra_buf != NULL) {
 
413
                stream_add_data(astream, extra_buf->data, extra_buf->used);
 
414
                buffer_free(&extra_buf);
 
415
        }
 
416
        return 0;
 
417
}
 
418
 
 
419
static int astream_part_finish(struct attachment_istream *astream)
 
420
{
 
421
        struct attachment_istream_part *part = &astream->part;
 
422
        struct istream_attachment_info info;
 
423
        struct istream *input;
 
424
        struct ostream *output;
 
425
        string_t *digest_str;
 
426
        const unsigned char *data;
 
427
        size_t size;
 
428
        int ret = 0;
 
429
 
 
430
        if (o_stream_nfinish(part->temp_output) < 0) {
 
431
                i_error("istream-attachment: write(%s) failed: %m",
 
432
                        o_stream_get_name(part->temp_output));
 
433
                return -1;
 
434
        }
 
435
 
 
436
        memset(&info, 0, sizeof(info));
 
437
        info.start_offset = astream->part.start_offset;
 
438
        /* base64_bytes contains how many valid base64 bytes there are so far.
 
439
           if the base64 ends properly, it'll specify how much of the MIME part
 
440
           is saved as an attachment. the rest of the data (typically
 
441
           linefeeds) is added back to main stream */
 
442
        info.encoded_size = part->base64_bytes;
 
443
        /* get the hash before base64-decoder resets it */
 
444
        digest_str = t_str_new(128);
 
445
        hash_format_write(astream->set.hash_format, digest_str);
 
446
        info.hash = str_c(digest_str);
 
447
 
 
448
        /* if it looks like we can decode base64 without any data loss,
 
449
           do it and write the decoded data to another temp file. */
 
450
        if (!part->base64_failed) {
 
451
                if (part->base64_state == BASE64_STATE_0 &&
 
452
                    part->base64_bytes > 0) {
 
453
                        /* there is no trailing LF or '=' characters,
 
454
                           but it's not completely empty */
 
455
                        part->base64_state = BASE64_STATE_EOM;
 
456
                }
 
457
                if (part->base64_state == BASE64_STATE_EOM) {
 
458
                        /* base64 data looks ok. */
 
459
                        if (astream_decode_base64(astream) < 0)
 
460
                                part->base64_failed = TRUE;
 
461
                } else {
 
462
                        part->base64_failed = TRUE;
 
463
                }
 
464
        }
 
465
 
 
466
        /* open attachment output file */
 
467
        info.part = astream->cur_part;
 
468
        if (!part->base64_failed) {
 
469
                info.base64_blocks_per_line = part->base64_line_blocks;
 
470
                info.base64_have_crlf = part->base64_have_crlf;
 
471
                /* base64-decoder updated the hash, use it */
 
472
                str_truncate(digest_str, 0);
 
473
                hash_format_write(astream->set.hash_format, digest_str);
 
474
                info.hash = str_c(digest_str);
 
475
        } else {
 
476
                /* couldn't decode base64, so write the entire MIME part
 
477
                   as attachment */
 
478
                info.encoded_size = part->temp_output->offset;
 
479
        }
 
480
        if (astream->set.open_attachment_ostream(&info, &output,
 
481
                                                 astream->context) < 0)
 
482
                return -1;
 
483
 
 
484
        /* copy data to attachment from temp file */
 
485
        input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
 
486
        while (i_stream_read_data(input, &data, &size, 0) > 0) {
 
487
                o_stream_nsend(output, data, size);
 
488
                i_stream_skip(input, size);
 
489
        }
 
490
 
 
491
        if (input->stream_errno != 0) {
 
492
                i_error("istream-attachment: read(%s) failed: %m",
 
493
                        i_stream_get_name(input));
 
494
                ret = -1;
 
495
        }
 
496
        i_stream_destroy(&input);
 
497
 
 
498
        if (astream->set.close_attachment_ostream(output, ret == 0,
 
499
                                                  astream->context) < 0)
 
500
                ret = -1;
 
501
        return ret;
 
502
}
 
503
 
 
504
static void astream_part_reset(struct attachment_istream *astream)
 
505
{
 
506
        struct attachment_istream_part *part = &astream->part;
 
507
 
 
508
        if (part->temp_output != NULL)
 
509
                o_stream_destroy(&part->temp_output);
 
510
        if (part->temp_fd != -1)
 
511
                i_close_fd(&part->temp_fd);
 
512
 
 
513
        i_free_and_null(part->content_type);
 
514
        i_free_and_null(part->content_disposition);
 
515
        if (part->part_buf != NULL)
 
516
                buffer_free(&part->part_buf);
 
517
 
 
518
        memset(part, 0, sizeof(*part));
 
519
        part->temp_fd = -1;
 
520
        hash_format_reset(astream->set.hash_format);
 
521
}
 
522
 
 
523
static int
 
524
astream_end_of_part(struct attachment_istream *astream)
 
525
{
 
526
        struct attachment_istream_part *part = &astream->part;
 
527
        size_t old_size;
 
528
        int ret = 0;
 
529
 
 
530
        /* MIME part changed. we're now parsing the end of a boundary,
 
531
           possibly followed by message epilogue */
 
532
        switch (part->state) {
 
533
        case MAIL_ATTACHMENT_STATE_NO:
 
534
                break;
 
535
        case MAIL_ATTACHMENT_STATE_MAYBE:
 
536
                /* MIME part wasn't large enough to be an attachment */
 
537
                if (part->part_buf != NULL) {
 
538
                        stream_add_data(astream, part->part_buf->data,
 
539
                                        part->part_buf->used);
 
540
                        ret = part->part_buf->used > 0 ? 1 : 0;
 
541
                }
 
542
                break;
 
543
        case MAIL_ATTACHMENT_STATE_YES:
 
544
                old_size = astream->istream.pos - astream->istream.skip;
 
545
                if (astream_part_finish(astream) < 0)
 
546
                        ret = -1;
 
547
                else {
 
548
                        /* finished base64 may have added a few more trailing
 
549
                           bytes to the stream */
 
550
                        ret = astream->istream.pos -
 
551
                                astream->istream.skip - old_size;
 
552
                }
 
553
                break;
 
554
        }
 
555
        part->state = MAIL_ATTACHMENT_STATE_NO;
 
556
        astream_part_reset(astream);
 
557
        return ret;
 
558
}
 
559
 
 
560
static int astream_read_next(struct attachment_istream *astream, bool *retry_r)
 
561
{
 
562
        struct istream_private *stream = &astream->istream;
 
563
        struct message_block block;
 
564
        size_t old_size, new_size;
 
565
        int ret;
 
566
 
 
567
        *retry_r = FALSE;
 
568
 
 
569
        if (stream->pos - stream->skip >= stream->max_buffer_size)
 
570
                return -2;
 
571
 
 
572
        old_size = stream->pos - stream->skip;
 
573
        switch (message_parser_parse_next_block(astream->parser, &block)) {
 
574
        case -1:
 
575
                /* done / error */
 
576
                ret = astream_end_of_part(astream);
 
577
                if (ret > 0) {
 
578
                        /* final data */
 
579
                        new_size = stream->pos - stream->skip;
 
580
                        return new_size - old_size;
 
581
                }
 
582
                stream->istream.eof = TRUE;
 
583
                stream->istream.stream_errno = stream->parent->stream_errno;
 
584
 
 
585
                if (ret < 0)
 
586
                        stream->istream.stream_errno = EINVAL;
 
587
                astream->cur_part = NULL;
 
588
                return -1;
 
589
        case 0:
 
590
                /* need more data */
 
591
                return 0;
 
592
        default:
 
593
                break;
 
594
        }
 
595
 
 
596
        if (block.part != astream->cur_part && astream->cur_part != NULL) {
 
597
                /* end of a MIME part */
 
598
                if (astream_end_of_part(astream) < 0) {
 
599
                        stream->istream.stream_errno = EINVAL;
 
600
                        return -1;
 
601
                }
 
602
        }
 
603
        astream->cur_part = block.part;
 
604
 
 
605
        if (block.hdr != NULL) {
 
606
                /* parsing a header */
 
607
                astream_parse_header(astream, block.hdr);
 
608
        } else if (block.size == 0) {
 
609
                /* end of headers */
 
610
                if (astream_want_attachment(astream, block.part)) {
 
611
                        astream->part.state = MAIL_ATTACHMENT_STATE_MAYBE;
 
612
                        astream->part.start_offset = stream->parent->v_offset;
 
613
                }
 
614
        } else {
 
615
                astream_add_body(astream, &block);
 
616
        }
 
617
        new_size = stream->pos - stream->skip;
 
618
        *retry_r = new_size == old_size;
 
619
        return new_size - old_size;
 
620
}
 
621
 
 
622
static ssize_t
 
623
i_stream_attachment_extractor_read(struct istream_private *stream)
 
624
{
 
625
        struct attachment_istream *astream =
 
626
                (struct attachment_istream *)stream;
 
627
        bool retry;
 
628
        ssize_t ret;
 
629
 
 
630
        do {
 
631
                ret = astream_read_next(astream, &retry);
 
632
        } while (retry && astream->set.drain_parent_input);
 
633
 
 
634
        astream->retry_read = retry;
 
635
        return ret;
 
636
}
 
637
 
 
638
static void i_stream_attachment_extractor_close(struct iostream_private *stream,
 
639
                                                bool close_parent)
 
640
{
 
641
        struct attachment_istream *astream =
 
642
                (struct attachment_istream *)stream;
 
643
        struct message_part *parts;
 
644
        int ret;
 
645
 
 
646
        if (astream->parser != NULL) {
 
647
                ret = message_parser_deinit(&astream->parser, &parts);
 
648
                i_assert(ret == 0); /* we didn't use preparsed message_parts */
 
649
        }
 
650
        hash_format_deinit_free(&astream->set.hash_format);
 
651
        if (astream->pool != NULL)
 
652
                pool_unref(&astream->pool);
 
653
        if (close_parent)
 
654
                i_stream_close(astream->istream.parent);
 
655
}
 
656
 
 
657
struct istream *
 
658
i_stream_create_attachment_extractor(struct istream *input,
 
659
                                     struct istream_attachment_settings *set,
 
660
                                     void *context)
 
661
{
 
662
        struct attachment_istream *astream;
 
663
 
 
664
        i_assert(set->min_size > 0);
 
665
        i_assert(set->hash_format != NULL);
 
666
        i_assert(set->open_attachment_ostream != NULL);
 
667
        i_assert(set->close_attachment_ostream != NULL);
 
668
 
 
669
        astream = i_new(struct attachment_istream, 1);
 
670
        astream->part.temp_fd = -1;
 
671
        astream->set = *set;
 
672
        astream->context = context;
 
673
        astream->retry_read = TRUE;
 
674
 
 
675
        /* make sure the caller doesn't try to double-free this */
 
676
        set->hash_format = NULL;
 
677
 
 
678
        astream->istream.max_buffer_size = input->real_stream->max_buffer_size;
 
679
 
 
680
        astream->istream.read = i_stream_attachment_extractor_read;
 
681
        astream->istream.iostream.close = i_stream_attachment_extractor_close;
 
682
 
 
683
        astream->istream.istream.readable_fd = FALSE;
 
684
        astream->istream.istream.blocking = input->blocking;
 
685
        astream->istream.istream.seekable = FALSE;
 
686
 
 
687
        astream->pool = pool_alloconly_create("istream attachment", 1024);
 
688
        astream->parser = message_parser_init(astream->pool, input, 0,
 
689
                                MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
 
690
                                MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
 
691
        return i_stream_create(&astream->istream, input,
 
692
                               i_stream_get_fd(input));
 
693
}
 
694
 
 
695
bool i_stream_attachment_extractor_can_retry(struct istream *input)
 
696
{
 
697
        struct attachment_istream *astream =
 
698
                (struct attachment_istream *)input->real_stream;
 
699
 
 
700
        return astream->retry_read;
 
701
}