diff options
Diffstat (limited to 'src/lib-mail/ostream-dot.c')
-rw-r--r-- | src/lib-mail/ostream-dot.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/lib-mail/ostream-dot.c b/src/lib-mail/ostream-dot.c new file mode 100644 index 0000000..f5bfa72 --- /dev/null +++ b/src/lib-mail/ostream-dot.c @@ -0,0 +1,235 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ostream-private.h" +#include "ostream-dot.h" + +enum dot_ostream_state { + STREAM_STATE_INIT = 0, + STREAM_STATE_NONE, + STREAM_STATE_CR, + STREAM_STATE_CRLF, + STREAM_STATE_DONE +}; + +struct dot_ostream { + struct ostream_private ostream; + + enum dot_ostream_state state; + bool force_extra_crlf; +}; + +static int o_stream_dot_finish(struct ostream_private *stream) +{ + struct dot_ostream *dstream = (struct dot_ostream *)stream; + int ret; + + if (dstream->state == STREAM_STATE_DONE) + return 1; + + if (o_stream_get_buffer_avail_size(stream->parent) < 5) { + /* make space for the dot line */ + if ((ret = o_stream_flush(stream->parent)) <= 0) { + if (ret < 0) + o_stream_copy_error_from_parent(stream); + return ret; + } + } + + if (dstream->state == STREAM_STATE_CRLF && + !dstream->force_extra_crlf) { + ret = o_stream_send(stream->parent, ".\r\n", 3); + i_assert(ret == 3); + } else { + ret = o_stream_send(stream->parent, "\r\n.\r\n", 5); + i_assert(ret == 5); + } + dstream->state = STREAM_STATE_DONE; + return 1; +} + +static int +o_stream_dot_flush(struct ostream_private *stream) +{ + int ret; + + if (stream->finished) { + if ((ret = o_stream_dot_finish(stream)) <= 0) + return ret; + } + + return o_stream_flush_parent(stream); +} + +static void +o_stream_dot_close(struct iostream_private *stream, bool close_parent) +{ + struct dot_ostream *dstream = (struct dot_ostream *)stream; + + if (close_parent) + o_stream_close(dstream->ostream.parent); +} + +static ssize_t +o_stream_dot_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct dot_ostream *dstream = (struct dot_ostream *)stream; + ARRAY(struct const_iovec) iov_arr; + const struct const_iovec *iov_new; + size_t max_bytes, sent, added; + unsigned int count, i; + ssize_t ret; + + i_assert(dstream->state != STREAM_STATE_DONE); + + if ((ret=o_stream_flush(stream->parent)) <= 0) { + /* error / we still couldn't flush existing data to + parent stream. */ + if (ret < 0) + o_stream_copy_error_from_parent(stream); + return ret; + } + + /* check for dots */ + t_array_init(&iov_arr, iov_count + 32); + max_bytes = o_stream_get_buffer_avail_size(stream->parent); + i_assert(max_bytes > 0); /* FIXME: not supported currently */ + + sent = added = 0; + for (i = 0; i < iov_count && max_bytes > 0; i++) { + size_t size = iov[i].iov_len, chunk; + const char *data = iov[i].iov_base, *p, *pend; + struct const_iovec iovn; + + p = data; + pend = CONST_PTR_OFFSET(data, size); + for (; p < pend && (size_t)(p-data)+2 < max_bytes; p++) { + char add = 0; + + switch (dstream->state) { + /* none */ + case STREAM_STATE_NONE: + switch (*p) { + case '\n': + dstream->state = STREAM_STATE_CRLF; + /* add missing CR */ + add = '\r'; + break; + case '\r': + dstream->state = STREAM_STATE_CR; + break; + } + break; + /* got CR */ + case STREAM_STATE_CR: + switch (*p) { + case '\r': + break; + case '\n': + dstream->state = STREAM_STATE_CRLF; + break; + default: + dstream->state = STREAM_STATE_NONE; + break; + } + break; + /* got CRLF, or the first line */ + case STREAM_STATE_INIT: + case STREAM_STATE_CRLF: + switch (*p) { + case '\r': + dstream->state = STREAM_STATE_CR; + break; + case '\n': + dstream->state = STREAM_STATE_CRLF; + /* add missing CR */ + add = '\r'; + break; + case '.': + /* add dot */ + add = '.'; + /* fall through */ + default: + dstream->state = STREAM_STATE_NONE; + break; + } + break; + case STREAM_STATE_DONE: + i_unreached(); + } + + if (add != 0) { + chunk = (size_t)(p - data); + if (chunk > 0) { + /* forward chunk to new iovec */ + iovn.iov_base = data; + iovn.iov_len = chunk; + array_push_back(&iov_arr, &iovn); + data = p; + i_assert(max_bytes >= chunk); + max_bytes -= chunk; + sent += chunk; + } + /* insert byte (substitute one with pair) */ + data++; + iovn.iov_base = (add == '\r' ? "\r\n" : ".."); + iovn.iov_len = 2; + array_push_back(&iov_arr, &iovn); + i_assert(max_bytes >= 2); + max_bytes -= 2; + added++; + sent++; + } + } + + if (max_bytes == 0) + break; + chunk = ((size_t)(p-data) >= max_bytes ? + max_bytes : (size_t)(p - data)); + if (chunk > 0) { + iovn.iov_base = data; + iovn.iov_len = chunk; + array_push_back(&iov_arr, &iovn); + i_assert(max_bytes >= chunk); + max_bytes -= chunk; + sent += chunk; + } + } + + /* send */ + iov_new = array_get(&iov_arr, &count); + if (count == 0) { + ret = 0; + } else if ((ret=o_stream_sendv(stream->parent, iov_new, count)) <= 0) { + i_assert(ret < 0); + o_stream_copy_error_from_parent(stream); + return -1; + } + + /* all must be sent */ + i_assert((size_t)ret == sent + added); + + stream->ostream.offset += sent; + return sent; +} + +struct ostream * +o_stream_create_dot(struct ostream *output, bool force_extra_crlf) +{ + struct dot_ostream *dstream; + + dstream = i_new(struct dot_ostream, 1); + dstream->ostream.sendv = o_stream_dot_sendv; + dstream->ostream.iostream.close = o_stream_dot_close; + dstream->ostream.flush = o_stream_dot_flush; + dstream->ostream.max_buffer_size = output->real_stream->max_buffer_size; + dstream->force_extra_crlf = force_extra_crlf; + (void)o_stream_create(&dstream->ostream, output, o_stream_get_fd(output)); + /* ostream-dot is always used inside another ostream that shouldn't + get finished when the "." line is written. Disable it here so all + of the callers don't have to set this. */ + o_stream_set_finish_also_parent(&dstream->ostream.ostream, FALSE); + return &dstream->ostream.ostream; +} |