Coverage Report

Created: 2026-06-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-mail/istream-dot.c
Line
Count
Source
1
/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "istream-private.h"
5
#include "istream-dot.h"
6
7
enum dot_state {
8
  DOT_STATE_SEEN_NONE     = 0,
9
  DOT_STATE_SEEN_CR     = 1,
10
  DOT_STATE_SEEN_CR_LF      = 2,
11
  DOT_STATE_SEEN_CR_LF_DOT    = 3,
12
  DOT_STATE_SEEN_CR_LF_DOT_CR = 4,
13
};
14
15
struct dot_istream {
16
  struct istream_private istream;
17
18
  char pending[3]; /* max. \r\n */
19
20
  /* how far in string "\r\n.\r" are we */
21
  enum dot_state state;
22
  /* state didn't actually start with \r */
23
  bool state_no_cr:1;
24
  /* state didn't contain \n either (only at the beginning of stream) */
25
  bool state_no_lf:1;
26
  /* we've seen the "." line, keep returning EOF */
27
  bool dot_eof:1;
28
29
  bool send_last_lf:1;
30
  bool accept_bare_lf:1;
31
};
32
33
static int i_stream_dot_read_some(struct dot_istream *dstream)
34
7.32k
{
35
7.32k
  struct istream_private *stream = &dstream->istream;
36
7.32k
  size_t size, avail;
37
7.32k
  ssize_t ret;
38
39
7.32k
  size = i_stream_get_data_size(stream->parent);
40
7.32k
  if (size == 0) {
41
4.44k
    ret = i_stream_read_memarea(stream->parent);
42
4.44k
    if (ret <= 0) {
43
590
      i_assert(ret != -2); /* 0 sized buffer can't be full */
44
590
      if (stream->parent->stream_errno != 0) {
45
0
        stream->istream.stream_errno =
46
0
          stream->parent->stream_errno;
47
590
      } else if (ret < 0 && stream->parent->eof) {
48
        /* we didn't see "." line */
49
403
        io_stream_set_error(&stream->iostream,
50
403
          "dot-input stream ends without '.' line");
51
403
        stream->istream.stream_errno = EPIPE;
52
403
      }
53
590
      return ret;
54
590
    }
55
3.85k
    size = i_stream_get_data_size(stream->parent);
56
3.85k
    i_assert(size != 0);
57
3.85k
  }
58
59
6.73k
  if (!i_stream_try_alloc(stream, size, &avail))
60
0
    return -2;
61
6.73k
  return 1;
62
6.73k
}
63
64
static bool flush_pending(struct dot_istream *dstream, size_t *destp)
65
6.83M
{
66
6.83M
  struct istream_private *stream = &dstream->istream;
67
6.83M
  size_t dest = *destp;
68
6.83M
  unsigned int i = 0;
69
70
16.1M
  for (; dstream->pending[i] != '\0' && dest < stream->buffer_size; i++)
71
9.29M
    stream->w_buffer[dest++] = dstream->pending[i];
72
6.83M
  memmove(dstream->pending, dstream->pending + i,
73
6.83M
    sizeof(dstream->pending) - i);
74
6.83M
  *destp = dest;
75
6.83M
  return dest < stream->buffer_size;
76
6.83M
}
77
78
static bool flush_dot_state(struct dot_istream *dstream, size_t *destp)
79
6.83M
{
80
6.83M
  unsigned int i = 0;
81
82
6.83M
  if (!dstream->state_no_cr)
83
6.83M
    dstream->pending[i++] = '\r';
84
6.83M
  if (dstream->state_no_lf)
85
1.88k
    dstream->state_no_lf = FALSE;
86
6.83M
  else if (dstream->state > DOT_STATE_SEEN_CR)
87
2.46M
    dstream->pending[i++] = '\n';
88
6.83M
  dstream->pending[i] = '\0';
89
90
6.83M
  if (dstream->state != DOT_STATE_SEEN_CR_LF_DOT_CR)
91
6.83M
    dstream->state = DOT_STATE_SEEN_NONE;
92
2.01k
  else {
93
    /* \r\n.\r seen, go back to \r state */
94
2.01k
    dstream->state = DOT_STATE_SEEN_CR;
95
2.01k
  }
96
6.83M
  return flush_pending(dstream, destp);
97
6.83M
}
98
99
static void i_stream_dot_eof(struct dot_istream *dstream, size_t *destp)
100
1.50k
{
101
1.50k
  if (dstream->send_last_lf) {
102
1.50k
    dstream->state = DOT_STATE_SEEN_CR_LF;
103
1.50k
    (void)flush_dot_state(dstream, destp);
104
1.50k
  }
105
1.50k
  dstream->dot_eof = TRUE;
106
1.50k
}
107
108
static ssize_t
109
i_stream_dot_return(struct istream_private *stream, size_t dest, ssize_t ret)
110
15.7k
{
111
15.7k
  if (dest != stream->pos) {
112
5.48k
    i_assert(dest > stream->pos);
113
5.48k
    ret = dest - stream->pos;
114
5.48k
    stream->pos = dest;
115
5.48k
  }
116
15.7k
  return ret;
117
15.7k
}
118
119
static ssize_t i_stream_dot_read(struct istream_private *stream)
120
8.83k
{
121
  /* @UNSAFE */
122
8.83k
  struct dot_istream *dstream =
123
8.83k
    container_of(stream, struct dot_istream, istream);
124
8.83k
  const unsigned char *data;
125
8.83k
  size_t i, dest, size, avail;
126
8.83k
  ssize_t ret, ret1;
127
128
8.83k
  if (dstream->pending[0] != '\0') {
129
116
    if (!i_stream_try_alloc(stream, 1, &avail))
130
0
      return -2;
131
116
    dest = stream->pos;
132
116
    (void)flush_pending(dstream, &dest);
133
8.71k
  } else {
134
8.71k
    dest = stream->pos;
135
8.71k
  }
136
137
8.83k
  if (dstream->dot_eof) {
138
1.50k
    stream->istream.eof = TRUE;
139
1.50k
    return i_stream_dot_return(stream, dest, -1);
140
1.50k
  }
141
142
  /* we have to update stream->pos before reading more data */
143
7.32k
  ret1 = i_stream_dot_return(stream, dest, 0);
144
7.32k
  if ((ret = i_stream_dot_read_some(dstream)) <= 0) {
145
590
    if (stream->istream.stream_errno != 0)
146
403
      return -1;
147
187
    if (ret1 != 0)
148
0
      return ret1;
149
187
    dest = stream->pos;
150
187
    if (ret == -1 && dstream->state != DOT_STATE_SEEN_NONE)
151
0
      (void)flush_dot_state(dstream, &dest);
152
187
    return i_stream_dot_return(stream, dest, ret);
153
187
  }
154
6.73k
  dest = stream->pos;
155
156
6.73k
  data = i_stream_get_data(stream->parent, &size);
157
9.61M
  for (i = 0; i < size && dest < stream->buffer_size; i++) {
158
9.60M
    switch (dstream->state) {
159
301k
    case DOT_STATE_SEEN_NONE: {
160
      /* ensure we don't read too far, since we can only
161
         fill w_buffer up to it's size */
162
301k
      size_t maxlen =
163
301k
        I_MIN(size - i, stream->buffer_size - dest);
164
301k
      size_t len;
165
301k
      if (dstream->accept_bare_lf)
166
0
        len = i_memcspn(data + i, maxlen, "\r\n", 2);
167
301k
      else /* we only need to find new CR */
168
301k
        len = i_memcspn(data + i, maxlen, "\r", 1);
169
170
      /* if there was data, copy it and try again */
171
301k
      if (len > 0) {
172
106k
        memcpy(PTR_OFFSET(stream->w_buffer, dest),
173
106k
               data + i, len);
174
106k
        dest += len;
175
106k
        i += len;
176
106k
      }
177
301k
      if (i == size || dest == stream->buffer_size)
178
3.01k
        goto end;
179
298k
      break;
180
301k
    }
181
6.83M
    case DOT_STATE_SEEN_CR:
182
      /* CR seen */
183
6.83M
      if (data[i] == '\n')
184
2.46M
        dstream->state = DOT_STATE_SEEN_CR_LF;
185
4.36M
      else {
186
4.36M
        if (!flush_dot_state(dstream, &dest))
187
415
          goto end;
188
4.36M
      }
189
6.83M
      break;
190
6.83M
    case DOT_STATE_SEEN_CR_LF:
191
      /* [CR]LF seen */
192
2.46M
      if (data[i] == '.')
193
5.33k
        dstream->state = DOT_STATE_SEEN_CR_LF_DOT;
194
2.46M
      else {
195
2.46M
        if (!flush_dot_state(dstream, &dest))
196
304
          goto end;
197
2.46M
      }
198
2.46M
      break;
199
2.46M
    case DOT_STATE_SEEN_CR_LF_DOT:
200
      /* [CR]LF. seen */
201
5.32k
      if (data[i] == '\r')
202
3.52k
        dstream->state = DOT_STATE_SEEN_CR_LF_DOT_CR;
203
1.79k
      else if (data[i] == '\n' && dstream->accept_bare_lf) {
204
        /* EOF */
205
0
        i_stream_dot_eof(dstream, &dest);
206
0
        i++;
207
0
        goto end;
208
1.79k
      } else {
209
        /* drop the initial dot */
210
1.79k
        if (!flush_dot_state(dstream, &dest))
211
12
          goto end;
212
1.79k
      }
213
5.30k
      break;
214
5.30k
    case DOT_STATE_SEEN_CR_LF_DOT_CR:
215
      /* [CR]LF.CR seen */
216
3.51k
      if (data[i] == '\n') {
217
        /* EOF */
218
1.50k
        i_stream_dot_eof(dstream, &dest);
219
1.50k
        i++;
220
1.50k
        goto end;
221
2.01k
      } else {
222
        /* drop the initial dot */
223
2.01k
        if (!flush_dot_state(dstream, &dest))
224
1
          goto end;
225
2.01k
      }
226
9.60M
    }
227
9.60M
    if (dstream->state == DOT_STATE_SEEN_NONE) {
228
7.12M
      if (data[i] == '\r') {
229
6.83M
        dstream->state = DOT_STATE_SEEN_CR;
230
6.83M
        dstream->state_no_cr = FALSE;
231
6.83M
      } else if (data[i] == '\n' && dstream->accept_bare_lf) {
232
0
        dstream->state = DOT_STATE_SEEN_CR_LF;
233
0
        dstream->state_no_cr = TRUE;
234
297k
      } else {
235
297k
        stream->w_buffer[dest++] = data[i];
236
297k
      }
237
7.12M
    }
238
9.60M
  }
239
6.73k
end:
240
6.73k
  i_stream_skip(stream->parent, i);
241
242
6.73k
  ret = i_stream_dot_return(stream, dest, 0) + ret1;
243
6.73k
  if (ret == 0)
244
1.24k
    return i_stream_dot_read(stream);
245
6.73k
  i_assert(ret > 0);
246
5.48k
  return ret;
247
5.48k
}
248
249
struct istream *
250
i_stream_create_dot(struct istream *input, enum istream_dot_flags flags)
251
1.91k
{
252
1.91k
  struct dot_istream *dstream;
253
254
1.91k
  dstream = i_new(struct dot_istream, 1);
255
1.91k
  dstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
256
1.91k
  dstream->istream.read = i_stream_dot_read;
257
258
1.91k
  dstream->istream.istream.readable_fd = FALSE;
259
1.91k
  dstream->istream.istream.blocking = input->blocking;
260
1.91k
  dstream->istream.istream.seekable = FALSE;
261
1.91k
  dstream->send_last_lf = HAS_NO_BITS(flags, ISTREAM_DOT_TRIM_TRAIL);
262
1.91k
  dstream->accept_bare_lf = HAS_ANY_BITS(flags, ISTREAM_DOT_LOOSE_EOT);
263
1.91k
  dstream->state = DOT_STATE_SEEN_CR_LF;
264
1.91k
  dstream->state_no_cr = TRUE;
265
1.91k
  dstream->state_no_lf = TRUE;
266
1.91k
  return i_stream_create(&dstream->istream, input,
267
1.91k
             i_stream_get_fd(input), 0);
268
1.91k
}