dovecot-2.2: Added istream-binary-converter to convert binary MI...

dovecot at dovecot.org dovecot at dovecot.org
Wed Jun 20 02:23:54 EEST 2012


details:   http://hg.dovecot.org/dovecot-2.2/rev/7b1378fe8820
changeset: 14606:7b1378fe8820
user:      Timo Sirainen <tss at iki.fi>
date:      Wed Jun 20 02:23:42 2012 +0300
description:
Added istream-binary-converter to convert binary MIME parts to base64.

diffstat:

 src/lib-mail/Makefile.am                     |    7 +
 src/lib-mail/istream-binary-converter.c      |  303 +++++++++++++++++++++++++++
 src/lib-mail/istream-binary-converter.h      |    6 +
 src/lib-mail/test-istream-binary-converter.c |  152 +++++++++++++
 4 files changed, 468 insertions(+), 0 deletions(-)

diffs (truncated from 511 to 300 lines):

diff -r 6846c2e50eba -r 7b1378fe8820 src/lib-mail/Makefile.am
--- a/src/lib-mail/Makefile.am	Wed Jun 20 02:22:27 2012 +0300
+++ b/src/lib-mail/Makefile.am	Wed Jun 20 02:23:42 2012 +0300
@@ -6,6 +6,7 @@
 	-I$(top_srcdir)/src/lib-charset
 
 libmail_la_SOURCES = \
+	istream-binary-converter.c \
 	istream-dot.c \
 	istream-header-filter.c \
 	mail-user-hash.c \
@@ -27,6 +28,7 @@
 	rfc822-parser.c
 
 headers = \
+	istream-binary-converter.h \
 	istream-dot.h \
 	istream-header-filter.h \
 	mail-user-hash.h \
@@ -53,6 +55,7 @@
 
 test_programs = \
 	test-istream-dot \
+	test-istream-binary-converter \
 	test-istream-header-filter \
 	test-mbox-from \
 	test-message-address \
@@ -76,6 +79,10 @@
 test_istream_dot_LDADD = istream-dot.lo $(test_libs)
 test_istream_dot_DEPENDENCIES = istream-dot.lo $(test_libs)
 
+test_istream_binary_converter_SOURCES = test-istream-binary-converter.c
+test_istream_binary_converter_LDADD = istream-binary-converter.lo message-parser.lo message-header-parser.lo message-size.lo rfc822-parser.lo rfc2231-parser.lo $(test_libs)
+test_istream_binary_converter_DEPENDENCIES = istream-binary-converter.lo message-parser.lo message-header-parser.lo message-size.lo rfc822-parser.lo rfc2231-parser.lo $(test_libs)
+
 test_istream_header_filter_SOURCES = test-istream-header-filter.c
 test_istream_header_filter_LDADD = istream-header-filter.lo message-header-parser.lo $(test_libs)
 test_istream_header_filter_DEPENDENCIES = istream-header-filter.lo message-header-parser.lo $(test_libs)
diff -r 6846c2e50eba -r 7b1378fe8820 src/lib-mail/istream-binary-converter.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/istream-binary-converter.c	Wed Jun 20 02:23:42 2012 +0300
@@ -0,0 +1,303 @@
+/* Copyright (c) 2012 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)
+
+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;
+	unsigned int cte_header_len;
+	unsigned int content_type_seen:1;
+};
+
+static void *
+stream_alloc_data(struct binary_converter_istream *bstream, size_t size)
+{
+	struct istream_private *stream = &bstream->istream;
+	size_t old_size, avail_size;
+
+	(void)i_stream_get_buffer_space(stream, size, &avail_size);
+	if (avail_size < size) {
+		old_size = stream->buffer_size;
+		stream->buffer_size = nearest_power(stream->pos + size);
+		stream->w_buffer = i_realloc(stream->w_buffer, old_size,
+					     stream->buffer_size);
+		stream->buffer = stream->w_buffer;
+		(void)i_stream_get_buffer_space(stream, size, &avail_size);
+		i_assert(avail_size >= size);
+	}
+	return stream->w_buffer + stream->pos;
+}
+
+static void stream_add_data(struct binary_converter_istream *bstream,
+			    const void *data, size_t size)
+{
+	if (bstream->hdr_buf != NULL) {
+		buffer_append(bstream->hdr_buf, data, size);
+	} else if (size > 0) {
+		memcpy(stream_alloc_data(bstream, size), data, size);
+		bstream->istream.pos += size;
+	}
+}
+
+static void stream_encode_base64(struct binary_converter_istream *bstream,
+				 const void *_data, size_t size)
+{
+	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(stream_alloc_data(bstream, 2), "\r\n", 2);
+			bstream->istream.pos += 2;
+			bstream->base64_block_pos = 0;
+		}
+
+		dest = stream_alloc_data(bstream, BASE64_BLOCK_SIZE);
+		buffer_create_data(&buf, dest, BASE64_BLOCK_SIZE);
+		base64_encode(base64_block, base64_block_len, &buf);
+		bstream->istream.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(stream_alloc_data(bstream, 2), "\r\n", 2);
+			bstream->istream.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 = stream_alloc_data(bstream, max_encoded_size);
+		buffer_create_data(&buf, dest, max_encoded_size);
+		base64_encode(data, encode_size, &buf);
+		bstream->istream.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 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_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 >= stream->max_buffer_size)
+		return -2;
+
+	switch (message_parser_parse_next_block(bstream->parser, &block)) {
+	case -1:
+		/* done / error */
+		stream->istream.eof = TRUE;
+		stream->istream.stream_errno = stream->parent->stream_errno;
+		return -1;
+	case 0:
+		/* need more data */
+		return 0;
+	default:
+		break;
+	}
+
+	old_size = stream->pos - stream->skip;
+
+	if (block.part != bstream->convert_part &&
+	    bstream->convert_part != NULL) {
+		/* end of base64 encoded part */
+		stream_encode_base64(bstream, NULL, 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 */
+			buffer_t *buf = bstream->hdr_buf;
+			const unsigned char *data;
+
+			bstream->hdr_buf = NULL;
+			if (!part_can_convert(block.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);
+			}
+			stream_add_data(bstream, "\r\n", 2);
+			buffer_free(&buf);
+		} 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;


More information about the dovecot-cvs mailing list