changeset 9514:f068c8a19013 HEAD

Added dot istream for reading SMTP DATA-style input.
author Timo Sirainen <tss@iki.fi>
date Tue, 23 Jun 2009 17:30:16 -0400
parents 0b7617d66ab1
children 70b96df05e9a
files src/lib-mail/Makefile.am src/lib-mail/istream-dot.c src/lib-mail/istream-dot.h src/lib-mail/test-istream-dot.c
diffstat 4 files changed, 374 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-mail/Makefile.am	Tue Jun 23 14:45:01 2009 -0400
+++ b/src/lib-mail/Makefile.am	Tue Jun 23 17:30:16 2009 -0400
@@ -6,6 +6,7 @@
 	-I$(top_srcdir)/src/lib-charset
 
 libmail_la_SOURCES = \
+	istream-dot.c \
 	istream-header-filter.c \
 	mbox-from.c \
 	message-address.c \
@@ -24,6 +25,7 @@
 	rfc822-parser.c
 
 headers = \
+	istream-dot.h \
 	istream-header-filter.h \
 	mbox-from.h \
 	mail-types.h \
@@ -50,6 +52,7 @@
 endif
 
 test_programs = \
+	test-istream-dot \
 	test-istream-header-filter \
 	test-mbox-from \
 	test-message-address \
@@ -65,6 +68,10 @@
 	../lib-test/libtest.la \
 	../lib/liblib.la
 
+test_istream_dot_SOURCES = test-istream-dot.c
+test_istream_dot_LDADD = istream-dot.lo $(test_libs)
+test_istream_dot_DEPENDENCIES = istream-dot.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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/istream-dot.c	Tue Jun 23 17:30:16 2009 -0400
@@ -0,0 +1,258 @@
+/* Copyright (c) 2007-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-internal.h"
+#include "istream-dot.h"
+
+struct dot_istream {
+	struct istream_private istream;
+
+	char pending[3]; /* max. \r\n */
+
+	/* how far in string "\r\n.\r" are we */
+	unsigned int state;
+	/* state didn't actually start with \r */
+	unsigned int state_no_cr:1;
+	/* state didn't contain \n either (only at the beginnign of stream) */
+	unsigned int state_no_lf:1;
+	/* we've seen the "." line, keep returning EOF */
+	unsigned int dot_eof:1;
+
+	unsigned int send_last_lf:1;
+};
+
+static void i_stream_dot_destroy(struct iostream_private *stream)
+{
+	struct dot_istream *dstream = (struct dot_istream *)stream;
+
+	i_free(dstream->istream.w_buffer);
+	i_stream_unref(&dstream->istream.parent);
+}
+
+static void
+i_stream_dot_set_max_buffer_size(struct iostream_private *stream,
+				 size_t max_size)
+{
+	struct dot_istream *dstream = (struct dot_istream *)stream;
+
+	dstream->istream.max_buffer_size = max_size;
+	i_stream_set_max_buffer_size(dstream->istream.parent, max_size);
+}
+
+static int i_stream_dot_read_some(struct dot_istream *dstream)
+{
+	struct istream_private *stream = &dstream->istream;
+	size_t size;
+	ssize_t ret;
+
+	(void)i_stream_get_data(stream->parent, &size);
+	if (size == 0) {
+		ret = i_stream_read(stream->parent);
+		if (ret <= 0 && (ret != -2 || stream->skip == 0)) {
+			stream->istream.stream_errno =
+				stream->parent->stream_errno;
+			stream->istream.eof = stream->parent->eof;
+			return ret;
+		}
+		(void)i_stream_get_data(stream->parent, &size);
+		i_assert(size != 0);
+	}
+
+	if (!i_stream_get_buffer_space(stream, size, NULL))
+		return -2;
+	return 1;
+}
+
+static bool flush_pending(struct dot_istream *dstream, size_t *destp)
+{
+	struct istream_private *stream = &dstream->istream;
+	size_t dest = *destp;
+	unsigned int i = 0;
+
+	for (; dstream->pending[i] != '\0' && dest < stream->buffer_size; i++)
+		stream->w_buffer[dest++] = dstream->pending[i];
+	memmove(dstream->pending, dstream->pending + i,
+		sizeof(dstream->pending) - i);
+	*destp = dest;
+	return dest < stream->buffer_size;
+}
+
+static bool flush_dot_state(struct dot_istream *dstream, size_t *destp)
+{
+	unsigned int i = 0;
+
+	if (!dstream->state_no_cr)
+		dstream->pending[i++] = '\r';
+	if (dstream->state_no_lf)
+		dstream->state_no_lf = FALSE;
+	else if (dstream->state > 1)
+		dstream->pending[i++] = '\n';
+	dstream->pending[i] = '\0';
+
+	if (dstream->state != 4)
+		dstream->state = 0;
+	else {
+		/* \r\n.\r seen, go back to \r state */
+		dstream->state = 1;
+	}
+	return flush_pending(dstream, destp);
+}
+
+static void i_stream_dot_eof(struct dot_istream *dstream, size_t *destp)
+{
+	if (dstream->send_last_lf) {
+		dstream->state = 2;
+		(void)flush_dot_state(dstream, destp);
+	}
+	dstream->dot_eof = TRUE;
+}
+
+static ssize_t
+i_stream_dot_return(struct istream_private *stream, size_t dest, ssize_t ret)
+{
+	if (dest != stream->pos) {
+		i_assert(dest > stream->pos);
+		ret = dest - stream->pos;
+		stream->pos = dest;
+	}
+	return ret;
+}
+
+static ssize_t i_stream_dot_read(struct istream_private *stream)
+{
+	/* @UNSAFE */
+	struct dot_istream *dstream = (struct dot_istream *)stream;
+	const unsigned char *data;
+	size_t i, dest, size;
+	ssize_t ret;
+
+	dest = stream->pos;
+	if (dstream->pending[0] != '\0') {
+		if (!i_stream_get_buffer_space(stream, 1, NULL))
+			return -2;
+		(void)flush_pending(dstream, &dest);
+	}
+
+	if (dstream->dot_eof) {
+		stream->istream.eof = TRUE;
+		return i_stream_dot_return(stream, dest, -1);
+	}
+
+	if ((ret = i_stream_dot_read_some(dstream)) <= 0) {
+		if (ret == -1 && dstream->state != 0)
+			(void)flush_dot_state(dstream, &dest);
+		return i_stream_dot_return(stream, dest, ret);
+	}
+
+	data = i_stream_get_data(stream->parent, &size);
+	for (i = 0; i < size && dest < stream->buffer_size; i++) {
+		switch (dstream->state) {
+		case 0:
+			break;
+		case 1:
+			/* CR seen */
+			if (data[i] == '\n')
+				dstream->state++;
+			else {
+				if (!flush_dot_state(dstream, &dest))
+					goto end;
+			}
+			break;
+		case 2:
+			/* [CR]LF seen */
+			if (data[i] == '.')
+				dstream->state++;
+			else {
+				if (!flush_dot_state(dstream, &dest))
+					goto end;
+			}
+			break;
+		case 3:
+			/* [CR]LF. seen */
+			if (data[i] == '\r')
+				dstream->state++;
+			else if (data[i] == '\n') {
+				/* EOF */
+				i_stream_dot_eof(dstream, &dest);
+				i++;
+				goto end;
+			} else {
+				/* drop the initial dot */
+				if (!flush_dot_state(dstream, &dest))
+					goto end;
+			}
+			break;
+		case 4:
+			/* [CR]LF.CR seen */
+			if (data[i] == '\n') {
+				/* EOF */
+				i_stream_dot_eof(dstream, &dest);
+				i++;
+				goto end;
+			} else {
+				/* drop the initial dot */
+				if (!flush_dot_state(dstream, &dest))
+					goto end;
+			}
+		}
+		if (dstream->state == 0) {
+			if (data[i] == '\r') {
+				dstream->state = 1;
+				dstream->state_no_cr = FALSE;
+			} else if (data[i] == '\n') {
+				dstream->state = 2;
+				dstream->state_no_cr = TRUE;
+			} else {
+				stream->w_buffer[dest++] = data[i];
+			}
+		}
+	}
+end:
+	i_stream_skip(stream->parent, i);
+
+	ret = i_stream_dot_return(stream, dest, 0);
+	if (ret == 0)
+		return i_stream_dot_read(stream);
+	i_assert(ret > 0);
+	return ret;
+}
+
+static void
+i_stream_dot_seek(struct istream_private *stream ATTR_UNUSED,
+		   uoff_t v_offset ATTR_UNUSED, bool mark ATTR_UNUSED)
+{
+	i_panic("dot-istream: seeking unsupported currently");
+}
+
+static const struct stat *
+i_stream_dot_stat(struct istream_private *stream, bool exact)
+{
+	return i_stream_stat(stream->parent, exact);
+}
+
+struct istream *i_stream_create_dot(struct istream *input, bool send_last_lf)
+{
+	struct dot_istream *dstream;
+
+	i_stream_ref(input);
+
+	dstream = i_new(struct dot_istream, 1);
+	dstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+
+	dstream->istream.iostream.destroy = i_stream_dot_destroy;
+	dstream->istream.iostream.set_max_buffer_size =
+		i_stream_dot_set_max_buffer_size;
+
+	dstream->istream.read = i_stream_dot_read;
+	dstream->istream.seek = i_stream_dot_seek;
+	dstream->istream.stat = i_stream_dot_stat;
+
+	dstream->istream.istream.blocking = input->blocking;
+	dstream->istream.istream.seekable = FALSE;
+	dstream->send_last_lf = send_last_lf;
+	dstream->state = 2;
+	dstream->state_no_cr = TRUE;
+	dstream->state_no_lf = TRUE;
+	return i_stream_create(&dstream->istream, input,
+			       i_stream_get_fd(input));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/istream-dot.h	Tue Jun 23 17:30:16 2009 -0400
@@ -0,0 +1,9 @@
+#ifndef ISTREAM_DOT_H
+#define ISTREAM_DOT_H
+
+/* Create input stream for reading SMTP DATA style message: Drop initial "."
+   from lines beginning with it. Return EOF on line that contains only ".".
+   If send_last_lf=FALSE, the trailing [CR]LF before "." line isn't returned. */
+struct istream *i_stream_create_dot(struct istream *input, bool send_last_lf);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/test-istream-dot.c	Tue Jun 23 17:30:16 2009 -0400
@@ -0,0 +1,100 @@
+/* Copyright (c) 2007-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-dot.h"
+#include "test-common.h"
+
+struct dot_test {
+	const char *input;
+	const char *output;
+	const char *parent_input;
+};
+
+static void test_istream_dot_one(const struct dot_test *test,
+				 bool send_last_lf, bool test_bufsize)
+{
+	struct istream *test_input, *input;
+	const unsigned char *data;
+	size_t size;
+	unsigned int i, input_len, output_len;
+
+	test_input = test_istream_create(test->input);
+	input = i_stream_create_dot(test_input, send_last_lf);
+
+	output_len = strlen(test->output);
+	if (!send_last_lf &&
+	    (test->input[input_len-1] == '\n' ||
+	     strstr(test->input, "\n.\n") != NULL ||
+	     strstr(test->input, "\n.\r\n") != NULL)) {
+		if (test->output[output_len-1] == '\n') {
+			output_len--;
+			if (test->output[output_len-1] == '\r')
+				output_len--;
+		}
+	}
+
+	input_len = strlen(test->input);
+	if (!test_bufsize) {
+		for (i = 1; i <= input_len; i++) {
+			test_istream_set_size(test_input, i);
+			(void)i_stream_read(input);
+		}
+	} else {
+		test_istream_set_size(test_input, input_len);
+		size = 0;
+		for (i = 1; i < output_len; i++) {
+			i_stream_set_max_buffer_size(input, i);
+			test_assert(i_stream_read(input) == 1);
+			test_assert(i_stream_read(input) == -2);
+			data = i_stream_get_data(input, &size);
+			test_assert(memcmp(data, test->output, size) == 0);
+		}
+		i_stream_set_max_buffer_size(input, i+2);
+		if (size < output_len)
+			test_assert(i_stream_read(input) == 1);
+		test_assert(i_stream_read(input) == -1);
+	}
+	data = i_stream_get_data(input, &size);
+	test_assert(size == output_len);
+	test_assert(memcmp(data, test->output, size) == 0);
+
+	data = i_stream_get_data(test_input, &size);
+	test_assert(size == strlen(test->parent_input));
+	test_assert(memcmp(data, test->parent_input, size) == 0);
+
+	i_stream_unref(&test_input);
+	i_stream_unref(&input);
+}
+
+static void test_istream_dot(void)
+{
+	static struct dot_test tests[] = {
+		{ "..foo\n..\n.foo\n.\nfoo", ".foo\n.\nfoo\n", "foo" },
+		{ "..foo\r\n..\r\n.foo\r\n.\r\nfoo", ".foo\r\n.\r\nfoo\r\n", "foo" },
+		{ "\r\n.\r\n", "\r\n", "" },
+		{ "\n.\r\n", "\n", "" },
+		{ "\n.\n", "\n", "" },
+		{ "\n.", "\n", "" },
+		{ ".", "", "" }
+	};
+	unsigned int i;
+
+	test_begin("dot istream");
+	for (i = 0; i < N_ELEMENTS(tests); i++) {
+		test_istream_dot_one(&tests[i], TRUE, TRUE);
+		test_istream_dot_one(&tests[i], TRUE, FALSE);
+		test_istream_dot_one(&tests[i], FALSE, TRUE);
+		test_istream_dot_one(&tests[i], FALSE, FALSE);
+	}
+	test_end();
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_istream_dot,
+		NULL
+	};
+	return test_run(test_functions);
+}