Mercurial > dovecot > core-2.2
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); +}