istream-dot.c revision a8c5a86d183db25a57bf193c06b41e092ec2e151
5717e8e12768ab1d667cd3e908e5f3ffe540655aTimo Sirainen/* Copyright (c) 2007-2014 Dovecot authors, see the included COPYING file */
5717e8e12768ab1d667cd3e908e5f3ffe540655aTimo Sirainen
70267341ccf430f6c77646e69c24e33d111b65a8Pascal Volk#include "lib.h"
70267341ccf430f6c77646e69c24e33d111b65a8Pascal Volk#include "istream-private.h"
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk#include "istream-dot.h"
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
3188bfdf2c36fed3cbbea5e3901850e34e174a56Timo Sirainenstruct dot_istream {
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk struct istream_private istream;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk char pending[3]; /* max. \r\n */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk /* how far in string "\r\n.\r" are we */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int state;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk /* state didn't actually start with \r */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int state_no_cr:1;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk /* state didn't contain \n either (only at the beginnign of stream) */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int state_no_lf:1;
0610b6dfbdcb6a8f234a6495ec243bcf996b5572Pascal Volk /* we've seen the "." line, keep returning EOF */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int dot_eof:1;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int send_last_lf:1;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk};
0610b6dfbdcb6a8f234a6495ec243bcf996b5572Pascal Volk
88c816e8be4e1a29bca8b67d67a92c67a33f3795Timo Sirainenstatic int i_stream_dot_read_some(struct dot_istream *dstream)
e0579d45910f8a4ed55aa670f136db183344a786Timo Sirainen{
ed1ad90a15ea48d43edef858da3a598adca8b1feTimo Sirainen struct istream_private *stream = &dstream->istream;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk size_t size, avail;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk ssize_t ret;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk size = i_stream_get_data_size(stream->parent);
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk if (size == 0) {
e0579d45910f8a4ed55aa670f136db183344a786Timo Sirainen ret = i_stream_read(stream->parent);
f6b42d6832887ee5a94deb278f924bae7216e660Pascal Volk if (ret <= 0 && (ret != -2 || stream->skip == 0)) {
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk if (stream->parent->stream_errno != 0) {
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk stream->istream.stream_errno =
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk stream->parent->stream_errno;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk } else if (ret < 0 && stream->parent->eof) {
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk /* we didn't see "." line */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk io_stream_set_error(&stream->iostream,
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk "dot-input stream ends without '.' line");
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk stream->istream.stream_errno = EPIPE;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk }
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk return ret;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk }
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk size = i_stream_get_data_size(stream->parent);
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk i_assert(size != 0);
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk }
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk if (!i_stream_try_alloc(stream, size, &avail))
44c6dbf0a48ebf3d18aec72849bd114b54f4b0aaPascal Volk return -2;
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk return 1;
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk}
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volkstatic bool flush_pending(struct dot_istream *dstream, size_t *destp)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk{
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk struct istream_private *stream = &dstream->istream;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk size_t dest = *destp;
0610b6dfbdcb6a8f234a6495ec243bcf996b5572Pascal Volk unsigned int i = 0;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk for (; dstream->pending[i] != '\0' && dest < stream->buffer_size; i++)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk stream->w_buffer[dest++] = dstream->pending[i];
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk memmove(dstream->pending, dstream->pending + i,
2f45711505ab4564ee8a64ed7af87ae725a3f5d8Timo Sirainen sizeof(dstream->pending) - i);
88c816e8be4e1a29bca8b67d67a92c67a33f3795Timo Sirainen *destp = dest;
e0579d45910f8a4ed55aa670f136db183344a786Timo Sirainen return dest < stream->buffer_size;
ed1ad90a15ea48d43edef858da3a598adca8b1feTimo Sirainen}
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volkstatic bool flush_dot_state(struct dot_istream *dstream, size_t *destp)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk{
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk unsigned int i = 0;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
e0579d45910f8a4ed55aa670f136db183344a786Timo Sirainen if (!dstream->state_no_cr)
f6b42d6832887ee5a94deb278f924bae7216e660Pascal Volk dstream->pending[i++] = '\r';
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk if (dstream->state_no_lf)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk dstream->state_no_lf = FALSE;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk else if (dstream->state > 1)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk dstream->pending[i++] = '\n';
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk dstream->pending[i] = '\0';
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk if (dstream->state != 4)
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk dstream->state = 0;
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk else {
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk /* \r\n.\r seen, go back to \r state */
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk dstream->state = 1;
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk }
28372fb7f889fbf9d0b5266f154f7f3004e26324Pascal Volk return flush_pending(dstream, destp);
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk}
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volk
fa9f5378ec558c451f1f1c47a4c41aa7c916c929Pascal Volkstatic void i_stream_dot_eof(struct dot_istream *dstream, size_t *destp)
70267341ccf430f6c77646e69c24e33d111b65a8Pascal Volk{
5717e8e12768ab1d667cd3e908e5f3ffe540655aTimo Sirainen if (dstream->send_last_lf) {
ea954a624a8c31a4d7f05ceaa2d7d3b7768da58bDennis Schridde 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, avail;
ssize_t ret, ret1;
if (dstream->pending[0] != '\0') {
if (!i_stream_try_alloc(stream, 1, &avail))
return -2;
dest = stream->pos;
(void)flush_pending(dstream, &dest);
} else {
dest = stream->pos;
}
if (dstream->dot_eof) {
stream->istream.eof = TRUE;
return i_stream_dot_return(stream, dest, -1);
}
/* we have to update stream->pos before reading more data */
ret1 = i_stream_dot_return(stream, dest, 0);
if ((ret = i_stream_dot_read_some(dstream)) <= 0) {
if (ret1 != 0)
return ret1;
dest = stream->pos;
if (ret == -1 && dstream->state != 0)
(void)flush_dot_state(dstream, &dest);
return i_stream_dot_return(stream, dest, ret);
}
dest = stream->pos;
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) + ret1;
if (ret == 0)
return i_stream_dot_read(stream);
i_assert(ret > 0);
return ret;
}
struct istream *i_stream_create_dot(struct istream *input, bool send_last_lf)
{
struct dot_istream *dstream;
dstream = i_new(struct dot_istream, 1);
dstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
dstream->istream.read = i_stream_dot_read;
dstream->istream.istream.readable_fd = FALSE;
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));
}