view src/lib-mail/ostream-dot.c @ 18137:3009a1a6f6d5

global: freshen copyright Robomatically: git ls-files | xargs perl -p -i -e 's/(\d+)-201[0-4]/$1-2015/g;s/ (201[0-4]) Dovecot/ $1-2015 Dovecot/' Happy 2015 everyone! Signed-off-by: Phil Carmody <phil@dovecot.fi>
author Phil Carmody <phil@dovecot.fi>
date Mon, 05 Jan 2015 22:20:10 +0200
parents 2866a692ec47
children ca24e6d34345
line wrap: on
line source

/* Copyright (c) 2013-2015 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,
};

struct dot_ostream {
	struct ostream_private ostream;

	enum dot_ostream_state state;
};

static void
o_stream_dot_close(struct iostream_private *stream,
				    bool close_parent)
{
	struct dot_ostream *dstream = (struct dot_ostream *)stream;

	if (dstream->state == STREAM_STATE_CRLF)
		(void)o_stream_send(dstream->ostream.parent, ".\r\n", 3);
	else
		(void)o_stream_send(dstream->ostream.parent, "\r\n.\r\n", 5);
	(void)o_stream_flush(&dstream->ostream.ostream);

	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;

	if ((ret=o_stream_flush(stream->parent)) <= 0) {
		/* error / we still couldn't flush existing data to
		   parent stream. */
		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); // FIXME: what if max_buffer_size is 0?
	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) < (max_bytes-2); 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 = '.';
				default:
					dstream->state = STREAM_STATE_NONE;
					break;
				}
				break;
			}

			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_append(&iov_arr, &iovn, 1);
					data = p;
					max_bytes -= chunk;
					sent += chunk;
				}
				/* insert byte (substitute one with pair) */
				data++;
				iovn.iov_base = (add == '\r' ? "\r\n" : "..");
				iovn.iov_len = 2;
				array_append(&iov_arr, &iovn, 1);
				max_bytes -= 2;
				added++;
				sent++;
			}
		}

		if (max_bytes == 0)
			break;
		chunk = ((size_t)(p-data) >= (max_bytes-2) ?
				max_bytes - 2 : (size_t)(p - data));	
		if (chunk > 0) {
			iovn.iov_base = data;
			iovn.iov_len = chunk;
			array_append(&iov_arr, &iovn, 1);
			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)
{
	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.max_buffer_size = output->real_stream->max_buffer_size;
	return o_stream_create(&dstream->ostream, output, o_stream_get_fd(output));
}