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