/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.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 */ bool state_no_cr:1; /* state didn't contain \n either (only at the beginnign of stream) */ bool state_no_lf:1; /* we've seen the "." line, keep returning EOF */ bool dot_eof:1; bool send_last_lf:1; }; static int i_stream_dot_read_some(struct dot_istream *dstream) { struct istream_private *stream = &dstream->istream; size_t size, avail; ssize_t ret; size = i_stream_get_data_size(stream->parent); if (size == 0) { ret = i_stream_read_memarea(stream->parent); if (ret <= 0) { i_assert(ret != -2); /* 0 sized buffer can't be full */ if (stream->parent->stream_errno != 0) { stream->istream.stream_errno = stream->parent->stream_errno; } else if (ret < 0 && stream->parent->eof) { /* we didn't see "." line */ io_stream_set_error(&stream->iostream, "dot-input stream ends without '.' line"); stream->istream.stream_errno = EPIPE; } return ret; } size = i_stream_get_data_size(stream->parent); i_assert(size != 0); } if (!i_stream_try_alloc(stream, size, &avail)) 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, 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 (stream->istream.stream_errno != 0) return -1; 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), 0); }