/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 | } |