1
/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
6
#include "istream-private.h"
7
#include "message-parser.h"
8
#include "istream-binary-converter.h"
10
#define BASE64_BLOCK_INPUT_SIZE 3
11
#define BASE64_BLOCK_SIZE 4
12
#define BASE64_BLOCKS_PER_LINE (76/BASE64_BLOCK_SIZE)
13
#define MAX_HDR_BUFFER_SIZE (1024*32)
15
struct binary_converter_istream {
16
struct istream_private istream;
19
struct message_parser_ctx *parser;
20
struct message_part *convert_part;
21
char base64_delayed[BASE64_BLOCK_INPUT_SIZE-1];
22
unsigned int base64_delayed_len;
23
unsigned int base64_block_pos;
26
unsigned int cte_header_len;
27
unsigned int content_type_seen:1;
30
static void stream_add_data(struct binary_converter_istream *bstream,
31
const void *data, size_t size);
33
static bool part_can_convert(const struct message_part *part)
35
/* some MUAs use "c-t-e: binary" for multiparts.
36
we don't want to convert them. */
37
return (part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0;
41
stream_finish_convert_decision(struct binary_converter_istream *bstream)
43
buffer_t *buf = bstream->hdr_buf;
44
const unsigned char *data;
46
bstream->hdr_buf = NULL;
47
if (!part_can_convert(bstream->convert_part)) {
48
bstream->convert_part = NULL;
49
stream_add_data(bstream, buf->data, buf->used);
51
stream_add_data(bstream,
52
"Content-Transfer-Encoding: base64\r\n", 35);
54
data = CONST_PTR_OFFSET(buf->data, bstream->cte_header_len);
55
stream_add_data(bstream, data,
56
buf->used - bstream->cte_header_len);
61
static void stream_add_data(struct binary_converter_istream *bstream,
62
const void *data, size_t size)
67
if (bstream->hdr_buf != NULL) {
68
if (bstream->hdr_buf->used + size <= MAX_HDR_BUFFER_SIZE) {
69
buffer_append(bstream->hdr_buf, data, size);
72
/* buffer is getting too large. just finish the decision. */
73
stream_finish_convert_decision(bstream);
76
memcpy(i_stream_alloc(&bstream->istream, size), data, size);
77
bstream->istream.pos += size;
80
static void stream_encode_base64(struct binary_converter_istream *bstream,
81
const void *_data, size_t size)
83
struct istream_private *stream = &bstream->istream;
84
const unsigned char *data = _data;
87
size_t encode_size, max_encoded_size;
88
unsigned char base64_block[BASE64_BLOCK_INPUT_SIZE];
89
unsigned int base64_block_len, missing_len, encode_blocks;
91
if (bstream->base64_delayed_len > 0) {
92
if (bstream->base64_delayed_len == 1 && size == 1) {
93
bstream->base64_delayed[1] = data[0];
94
bstream->base64_delayed_len++;
97
memcpy(base64_block, bstream->base64_delayed,
98
bstream->base64_delayed_len);
99
base64_block_len = bstream->base64_delayed_len;
103
missing_len = BASE64_BLOCK_INPUT_SIZE - base64_block_len;
104
i_assert(size >= missing_len);
105
memcpy(base64_block + base64_block_len,
109
base64_block_len = BASE64_BLOCK_INPUT_SIZE;
112
if (bstream->base64_block_pos == BASE64_BLOCKS_PER_LINE) {
113
memcpy(i_stream_alloc(stream, 2), "\r\n", 2);
115
bstream->base64_block_pos = 0;
118
dest = i_stream_alloc(stream, BASE64_BLOCK_SIZE);
119
buffer_create_from_data(&buf, dest, BASE64_BLOCK_SIZE);
120
base64_encode(base64_block, base64_block_len, &buf);
121
stream->pos += buf.used;
122
bstream->base64_block_pos++;
123
bstream->base64_delayed_len = 0;
126
while (size >= BASE64_BLOCK_INPUT_SIZE) {
127
if (bstream->base64_block_pos == BASE64_BLOCKS_PER_LINE) {
128
memcpy(i_stream_alloc(stream, 2), "\r\n", 2);
130
bstream->base64_block_pos = 0;
133
/* try to encode one full line of base64 blocks */
134
encode_size = I_MIN(size, BASE64_BLOCKS_PER_LINE*BASE64_BLOCK_SIZE);
135
if (encode_size % BASE64_BLOCK_INPUT_SIZE != 0)
136
encode_size -= encode_size % BASE64_BLOCK_INPUT_SIZE;
137
encode_blocks = encode_size/BASE64_BLOCK_INPUT_SIZE;
138
if (bstream->base64_block_pos + encode_blocks > BASE64_BLOCKS_PER_LINE) {
139
encode_blocks = BASE64_BLOCKS_PER_LINE -
140
bstream->base64_block_pos;
141
encode_size = encode_blocks * BASE64_BLOCK_INPUT_SIZE;
144
max_encoded_size = MAX_BASE64_ENCODED_SIZE(encode_size);
145
dest = i_stream_alloc(stream, max_encoded_size);
146
buffer_create_from_data(&buf, dest, max_encoded_size);
147
base64_encode(data, encode_size, &buf);
148
stream->pos += buf.used;
149
bstream->base64_block_pos += encode_blocks;
155
/* encode these when more data is available */
156
i_assert(size < BASE64_BLOCK_INPUT_SIZE);
157
memcpy(bstream->base64_delayed, data, size);
158
bstream->base64_delayed_len = size;
162
static void stream_add_hdr(struct binary_converter_istream *bstream,
163
const struct message_header_line *hdr)
165
if (!hdr->continued) {
166
stream_add_data(bstream, hdr->name, hdr->name_len);
167
stream_add_data(bstream, hdr->middle, hdr->middle_len);
170
stream_add_data(bstream, hdr->value, hdr->value_len);
171
if (!hdr->no_newline)
172
stream_add_data(bstream, "\r\n", 2);
175
static ssize_t i_stream_binary_converter_read(struct istream_private *stream)
178
struct binary_converter_istream *bstream =
179
(struct binary_converter_istream *)stream;
180
struct message_block block;
181
size_t old_size, new_size;
183
if (stream->pos - stream->skip >= stream->max_buffer_size)
186
switch (message_parser_parse_next_block(bstream->parser, &block)) {
189
stream->istream.eof = TRUE;
190
stream->istream.stream_errno = stream->parent->stream_errno;
199
old_size = stream->pos - stream->skip;
201
if (block.part != bstream->convert_part &&
202
bstream->convert_part != NULL) {
203
/* end of base64 encoded part */
204
stream_encode_base64(bstream, "", 0);
207
if (block.hdr != NULL) {
208
/* parsing a header */
209
if (strcasecmp(block.hdr->name, "Content-Type") == 0)
210
bstream->content_type_seen = TRUE;
212
if (strcasecmp(block.hdr->name, "Content-Transfer-Encoding") == 0 &&
213
!block.hdr->continued && !block.hdr->continues &&
214
block.hdr->value_len == 6 &&
215
i_memcasecmp(block.hdr->value, "binary", 6) == 0 &&
216
part_can_convert(block.part) &&
217
bstream->convert_part != block.part) {
218
/* looks like we want to convert this body part to
219
base64, but if we haven't seen Content-Type yet
220
delay the decision until we've read the rest of
222
i_assert(block.part != NULL);
223
bstream->convert_part = block.part;
224
bstream->base64_block_pos = 0;
225
if (!bstream->content_type_seen) {
226
i_assert(bstream->hdr_buf == NULL);
227
bstream->hdr_buf = buffer_create_dynamic(default_pool, 512);
228
stream_add_hdr(bstream, block.hdr);
229
bstream->cte_header_len = bstream->hdr_buf->used;
231
stream_add_data(bstream,
232
"Content-Transfer-Encoding: base64\r\n", 35);
234
} else if (block.hdr->eoh && bstream->hdr_buf != NULL) {
235
/* finish the decision about decoding */
236
stream_finish_convert_decision(bstream);
237
stream_add_data(bstream, "\r\n", 2);
239
stream_add_hdr(bstream, block.hdr);
241
} else if (block.size == 0) {
243
if (bstream->hdr_buf != NULL) {
244
/* message has no body */
245
bstream->convert_part = NULL;
246
stream_add_data(bstream, bstream->hdr_buf->data,
247
bstream->hdr_buf->used);
248
buffer_free(&bstream->hdr_buf);
250
bstream->content_type_seen = FALSE;
251
} else if (block.part == bstream->convert_part) {
252
/* convert body part to base64 */
253
stream_encode_base64(bstream, block.data, block.size);
255
stream_add_data(bstream, block.data, block.size);
257
new_size = stream->pos - stream->skip;
258
if (new_size == old_size)
259
return i_stream_binary_converter_read(stream);
260
return new_size - old_size;
263
static void i_stream_binary_converter_close(struct iostream_private *stream,
266
struct binary_converter_istream *bstream =
267
(struct binary_converter_istream *)stream;
268
struct message_part *parts;
270
if (bstream->parser != NULL)
271
(void)message_parser_deinit(&bstream->parser, &parts);
272
if (bstream->pool != NULL)
273
pool_unref(&bstream->pool);
275
i_stream_close(bstream->istream.parent);
278
struct istream *i_stream_create_binary_converter(struct istream *input)
280
struct binary_converter_istream *bstream;
282
bstream = i_new(struct binary_converter_istream, 1);
283
bstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
285
bstream->istream.read = i_stream_binary_converter_read;
286
bstream->istream.iostream.close = i_stream_binary_converter_close;
288
bstream->istream.istream.readable_fd = FALSE;
289
bstream->istream.istream.blocking = input->blocking;
290
bstream->istream.istream.seekable = FALSE;
292
bstream->pool = pool_alloconly_create("istream binary converter", 128);
293
bstream->parser = message_parser_init(bstream->pool, input, 0,
294
MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
295
MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
296
return i_stream_create(&bstream->istream, input,
297
i_stream_get_fd(input));