summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/istream-binary-converter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-mail/istream-binary-converter.c')
-rw-r--r--src/lib-mail/istream-binary-converter.c309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/lib-mail/istream-binary-converter.c b/src/lib-mail/istream-binary-converter.c
new file mode 100644
index 0000000..856b854
--- /dev/null
+++ b/src/lib-mail/istream-binary-converter.c
@@ -0,0 +1,309 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "base64.h"
+#include "istream-private.h"
+#include "message-parser.h"
+#include "istream-binary-converter.h"
+
+#define BASE64_BLOCK_INPUT_SIZE 3
+#define BASE64_BLOCK_SIZE 4
+#define BASE64_BLOCKS_PER_LINE (76/BASE64_BLOCK_SIZE)
+#define MAX_HDR_BUFFER_SIZE (1024*32)
+
+struct binary_converter_istream {
+ struct istream_private istream;
+
+ pool_t pool;
+ struct message_parser_ctx *parser;
+ struct message_part *convert_part;
+ char base64_delayed[BASE64_BLOCK_INPUT_SIZE-1];
+ unsigned int base64_delayed_len;
+ unsigned int base64_block_pos;
+
+ buffer_t *hdr_buf;
+ size_t cte_header_len;
+ bool content_type_seen:1;
+};
+
+static void stream_add_data(struct binary_converter_istream *bstream,
+ const void *data, size_t size);
+
+static bool part_can_convert(const struct message_part *part)
+{
+ /* some MUAs use "c-t-e: binary" for multiparts.
+ we don't want to convert them. */
+ return (part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0;
+}
+
+static void
+stream_finish_convert_decision(struct binary_converter_istream *bstream)
+{
+ buffer_t *buf = bstream->hdr_buf;
+ const unsigned char *data;
+
+ i_assert(bstream->convert_part != NULL);
+
+ bstream->hdr_buf = NULL;
+ if (!part_can_convert(bstream->convert_part)) {
+ bstream->convert_part = NULL;
+ stream_add_data(bstream, buf->data, buf->used);
+ } else {
+ stream_add_data(bstream,
+ "Content-Transfer-Encoding: base64\r\n", 35);
+
+ data = CONST_PTR_OFFSET(buf->data, bstream->cte_header_len);
+ stream_add_data(bstream, data,
+ buf->used - bstream->cte_header_len);
+ }
+ buffer_free(&buf);
+}
+
+static void stream_add_data(struct binary_converter_istream *bstream,
+ const void *data, size_t size)
+{
+ if (size == 0)
+ return;
+
+ if (bstream->hdr_buf != NULL) {
+ if (bstream->hdr_buf->used + size <= MAX_HDR_BUFFER_SIZE) {
+ buffer_append(bstream->hdr_buf, data, size);
+ return;
+ }
+ /* buffer is getting too large. just finish the decision. */
+ stream_finish_convert_decision(bstream);
+ }
+
+ memcpy(i_stream_alloc(&bstream->istream, size), data, size);
+ bstream->istream.pos += size;
+}
+
+static void stream_encode_base64(struct binary_converter_istream *bstream,
+ const void *_data, size_t size)
+{
+ struct istream_private *stream = &bstream->istream;
+ const unsigned char *data = _data;
+ buffer_t buf;
+ void *dest;
+ size_t encode_size, max_encoded_size;
+ unsigned char base64_block[BASE64_BLOCK_INPUT_SIZE];
+ unsigned int base64_block_len, missing_len, encode_blocks;
+
+ if (bstream->base64_delayed_len > 0) {
+ if (bstream->base64_delayed_len == 1 && size == 1) {
+ bstream->base64_delayed[1] = data[0];
+ bstream->base64_delayed_len++;
+ return;
+ }
+ memcpy(base64_block, bstream->base64_delayed,
+ bstream->base64_delayed_len);
+ base64_block_len = bstream->base64_delayed_len;
+ if (size == 0) {
+ /* finish base64 */
+ } else {
+ missing_len = BASE64_BLOCK_INPUT_SIZE - base64_block_len;
+ i_assert(size >= missing_len);
+ memcpy(base64_block + base64_block_len,
+ data, missing_len);
+ data += missing_len;
+ size -= missing_len;
+ base64_block_len = BASE64_BLOCK_INPUT_SIZE;
+ }
+
+ if (bstream->base64_block_pos == BASE64_BLOCKS_PER_LINE) {
+ memcpy(i_stream_alloc(stream, 2), "\r\n", 2);
+ stream->pos += 2;
+ bstream->base64_block_pos = 0;
+ }
+
+ dest = i_stream_alloc(stream, BASE64_BLOCK_SIZE);
+ buffer_create_from_data(&buf, dest, BASE64_BLOCK_SIZE);
+ base64_encode(base64_block, base64_block_len, &buf);
+ stream->pos += buf.used;
+ bstream->base64_block_pos++;
+ bstream->base64_delayed_len = 0;
+ }
+
+ while (size >= BASE64_BLOCK_INPUT_SIZE) {
+ if (bstream->base64_block_pos == BASE64_BLOCKS_PER_LINE) {
+ memcpy(i_stream_alloc(stream, 2), "\r\n", 2);
+ stream->pos += 2;
+ bstream->base64_block_pos = 0;
+ }
+
+ /* try to encode one full line of base64 blocks */
+ encode_size = I_MIN(size, BASE64_BLOCKS_PER_LINE*BASE64_BLOCK_SIZE);
+ if (encode_size % BASE64_BLOCK_INPUT_SIZE != 0)
+ encode_size -= encode_size % BASE64_BLOCK_INPUT_SIZE;
+ encode_blocks = encode_size/BASE64_BLOCK_INPUT_SIZE;
+ if (bstream->base64_block_pos + encode_blocks > BASE64_BLOCKS_PER_LINE) {
+ encode_blocks = BASE64_BLOCKS_PER_LINE -
+ bstream->base64_block_pos;
+ encode_size = encode_blocks * BASE64_BLOCK_INPUT_SIZE;
+ }
+
+ max_encoded_size = MAX_BASE64_ENCODED_SIZE(encode_size);
+ dest = i_stream_alloc(stream, max_encoded_size);
+ buffer_create_from_data(&buf, dest, max_encoded_size);
+ base64_encode(data, encode_size, &buf);
+ stream->pos += buf.used;
+ bstream->base64_block_pos += encode_blocks;
+
+ data += encode_size;
+ size -= encode_size;
+ }
+ if (size > 0) {
+ /* encode these when more data is available */
+ i_assert(size < BASE64_BLOCK_INPUT_SIZE);
+ memcpy(bstream->base64_delayed, data, size);
+ bstream->base64_delayed_len = size;
+ }
+}
+
+static void stream_add_hdr(struct binary_converter_istream *bstream,
+ const struct message_header_line *hdr)
+{
+ if (!hdr->continued) {
+ stream_add_data(bstream, hdr->name, hdr->name_len);
+ stream_add_data(bstream, hdr->middle, hdr->middle_len);
+ }
+
+ stream_add_data(bstream, hdr->value, hdr->value_len);
+ if (!hdr->no_newline)
+ stream_add_data(bstream, "\r\n", 2);
+}
+
+static ssize_t i_stream_binary_converter_read(struct istream_private *stream)
+{
+ /* @UNSAFE */
+ struct binary_converter_istream *bstream =
+ (struct binary_converter_istream *)stream;
+ struct message_block block;
+ size_t old_size, new_size;
+
+ if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
+ return -2;
+ old_size = stream->pos - stream->skip;
+
+ switch (message_parser_parse_next_block(bstream->parser, &block)) {
+ case -1:
+ /* done / error */
+ if (bstream->convert_part != NULL &&
+ bstream->base64_delayed_len > 0) {
+ /* flush any pending base64 output */
+ stream_encode_base64(bstream, "", 0);
+ new_size = stream->pos - stream->skip;
+ i_assert(old_size != new_size);
+ return new_size - old_size;
+ }
+ stream->istream.eof = TRUE;
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ return -1;
+ case 0:
+ /* need more data */
+ return 0;
+ default:
+ break;
+ }
+
+ if (block.part != bstream->convert_part &&
+ bstream->convert_part != NULL) {
+ /* end of base64 encoded part */
+ stream_encode_base64(bstream, "", 0);
+ }
+
+ if (block.hdr != NULL) {
+ /* parsing a header */
+ if (strcasecmp(block.hdr->name, "Content-Type") == 0)
+ bstream->content_type_seen = TRUE;
+
+ if (strcasecmp(block.hdr->name, "Content-Transfer-Encoding") == 0 &&
+ !block.hdr->continued && !block.hdr->continues &&
+ block.hdr->value_len == 6 &&
+ i_memcasecmp(block.hdr->value, "binary", 6) == 0 &&
+ part_can_convert(block.part) &&
+ bstream->convert_part != block.part) {
+ /* looks like we want to convert this body part to
+ base64, but if we haven't seen Content-Type yet
+ delay the decision until we've read the rest of
+ the header */
+ i_assert(block.part != NULL);
+ bstream->convert_part = block.part;
+ bstream->base64_block_pos = 0;
+ if (!bstream->content_type_seen) {
+ i_assert(bstream->hdr_buf == NULL);
+ bstream->hdr_buf = buffer_create_dynamic(default_pool, 512);
+ stream_add_hdr(bstream, block.hdr);
+ bstream->cte_header_len = bstream->hdr_buf->used;
+ } else {
+ stream_add_data(bstream,
+ "Content-Transfer-Encoding: base64\r\n", 35);
+ }
+ } else if (block.hdr->eoh && bstream->hdr_buf != NULL) {
+ /* finish the decision about decoding */
+ stream_finish_convert_decision(bstream);
+ stream_add_data(bstream, "\r\n", 2);
+ } else {
+ stream_add_hdr(bstream, block.hdr);
+ }
+ } else if (block.size == 0) {
+ /* end of header */
+ if (bstream->hdr_buf != NULL) {
+ /* message has no body */
+ bstream->convert_part = NULL;
+ stream_add_data(bstream, bstream->hdr_buf->data,
+ bstream->hdr_buf->used);
+ buffer_free(&bstream->hdr_buf);
+ }
+ bstream->content_type_seen = FALSE;
+ } else if (block.part == bstream->convert_part) {
+ /* convert body part to base64 */
+ stream_encode_base64(bstream, block.data, block.size);
+ } else {
+ stream_add_data(bstream, block.data, block.size);
+ }
+ new_size = stream->pos - stream->skip;
+ if (new_size == old_size)
+ return i_stream_binary_converter_read(stream);
+ return new_size - old_size;
+}
+
+static void i_stream_binary_converter_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct binary_converter_istream *bstream =
+ (struct binary_converter_istream *)stream;
+ struct message_part *parts;
+
+ if (bstream->parser != NULL) {
+ message_parser_deinit(&bstream->parser, &parts);
+ }
+ pool_unref(&bstream->pool);
+ if (close_parent)
+ i_stream_close(bstream->istream.parent);
+}
+
+struct istream *i_stream_create_binary_converter(struct istream *input)
+{
+ const struct message_parser_settings parser_set = {
+ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
+ MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES,
+ };
+ struct binary_converter_istream *bstream;
+
+ bstream = i_new(struct binary_converter_istream, 1);
+ bstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+
+ bstream->istream.read = i_stream_binary_converter_read;
+ bstream->istream.iostream.close = i_stream_binary_converter_close;
+
+ bstream->istream.istream.readable_fd = FALSE;
+ bstream->istream.istream.blocking = input->blocking;
+ bstream->istream.istream.seekable = FALSE;
+
+ bstream->pool = pool_alloconly_create("istream binary converter", 128);
+ bstream->parser = message_parser_init(bstream->pool, input, &parser_set);
+ return i_stream_create(&bstream->istream, input,
+ i_stream_get_fd(input), 0);
+}