1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "array.h"
5 #include "ostream-private.h"
6 #include "ostream-dot.h"
7
8 enum dot_ostream_state {
9 STREAM_STATE_INIT = 0,
10 STREAM_STATE_NONE,
11 STREAM_STATE_CR,
12 STREAM_STATE_CRLF,
13 STREAM_STATE_DONE
14 };
15
16 struct dot_ostream {
17 struct ostream_private ostream;
18
19 enum dot_ostream_state state;
20 bool force_extra_crlf;
21 };
22
o_stream_dot_finish(struct ostream_private * stream)23 static int o_stream_dot_finish(struct ostream_private *stream)
24 {
25 struct dot_ostream *dstream = (struct dot_ostream *)stream;
26 int ret;
27
28 if (dstream->state == STREAM_STATE_DONE)
29 return 1;
30
31 if (o_stream_get_buffer_avail_size(stream->parent) < 5) {
32 /* make space for the dot line */
33 if ((ret = o_stream_flush(stream->parent)) <= 0) {
34 if (ret < 0)
35 o_stream_copy_error_from_parent(stream);
36 return ret;
37 }
38 }
39
40 if (dstream->state == STREAM_STATE_CRLF &&
41 !dstream->force_extra_crlf) {
42 ret = o_stream_send(stream->parent, ".\r\n", 3);
43 i_assert(ret == 3);
44 } else {
45 ret = o_stream_send(stream->parent, "\r\n.\r\n", 5);
46 i_assert(ret == 5);
47 }
48 dstream->state = STREAM_STATE_DONE;
49 return 1;
50 }
51
52 static int
o_stream_dot_flush(struct ostream_private * stream)53 o_stream_dot_flush(struct ostream_private *stream)
54 {
55 int ret;
56
57 if (stream->finished) {
58 if ((ret = o_stream_dot_finish(stream)) <= 0)
59 return ret;
60 }
61
62 return o_stream_flush_parent(stream);
63 }
64
65 static void
o_stream_dot_close(struct iostream_private * stream,bool close_parent)66 o_stream_dot_close(struct iostream_private *stream, bool close_parent)
67 {
68 struct dot_ostream *dstream = (struct dot_ostream *)stream;
69
70 if (close_parent)
71 o_stream_close(dstream->ostream.parent);
72 }
73
74 static ssize_t
o_stream_dot_sendv(struct ostream_private * stream,const struct const_iovec * iov,unsigned int iov_count)75 o_stream_dot_sendv(struct ostream_private *stream,
76 const struct const_iovec *iov, unsigned int iov_count)
77 {
78 struct dot_ostream *dstream = (struct dot_ostream *)stream;
79 ARRAY(struct const_iovec) iov_arr;
80 const struct const_iovec *iov_new;
81 size_t max_bytes, sent, added;
82 unsigned int count, i;
83 ssize_t ret;
84
85 i_assert(dstream->state != STREAM_STATE_DONE);
86
87 if ((ret=o_stream_flush(stream->parent)) <= 0) {
88 /* error / we still couldn't flush existing data to
89 parent stream. */
90 o_stream_copy_error_from_parent(stream);
91 return ret;
92 }
93
94 /* check for dots */
95 t_array_init(&iov_arr, iov_count + 32);
96 max_bytes = o_stream_get_buffer_avail_size(stream->parent);
97 i_assert(max_bytes > 0); /* FIXME: not supported currently */
98
99 sent = added = 0;
100 for (i = 0; i < iov_count && max_bytes > 0; i++) {
101 size_t size = iov[i].iov_len, chunk;
102 const char *data = iov[i].iov_base, *p, *pend;
103 struct const_iovec iovn;
104
105 p = data;
106 pend = CONST_PTR_OFFSET(data, size);
107 for (; p < pend && (size_t)(p-data)+2 < max_bytes; p++) {
108 char add = 0;
109
110 switch (dstream->state) {
111 /* none */
112 case STREAM_STATE_NONE:
113 switch (*p) {
114 case '\n':
115 dstream->state = STREAM_STATE_CRLF;
116 /* add missing CR */
117 add = '\r';
118 break;
119 case '\r':
120 dstream->state = STREAM_STATE_CR;
121 break;
122 }
123 break;
124 /* got CR */
125 case STREAM_STATE_CR:
126 switch (*p) {
127 case '\r':
128 break;
129 case '\n':
130 dstream->state = STREAM_STATE_CRLF;
131 break;
132 default:
133 dstream->state = STREAM_STATE_NONE;
134 break;
135 }
136 break;
137 /* got CRLF, or the first line */
138 case STREAM_STATE_INIT:
139 case STREAM_STATE_CRLF:
140 switch (*p) {
141 case '\r':
142 dstream->state = STREAM_STATE_CR;
143 break;
144 case '\n':
145 dstream->state = STREAM_STATE_CRLF;
146 /* add missing CR */
147 add = '\r';
148 break;
149 case '.':
150 /* add dot */
151 add = '.';
152 /* fall through */
153 default:
154 dstream->state = STREAM_STATE_NONE;
155 break;
156 }
157 break;
158 case STREAM_STATE_DONE:
159 i_unreached();
160 }
161
162 if (add != 0) {
163 chunk = (size_t)(p - data);
164 if (chunk > 0) {
165 /* forward chunk to new iovec */
166 iovn.iov_base = data;
167 iovn.iov_len = chunk;
168 array_push_back(&iov_arr, &iovn);
169 data = p;
170 i_assert(max_bytes >= chunk);
171 max_bytes -= chunk;
172 sent += chunk;
173 }
174 /* insert byte (substitute one with pair) */
175 data++;
176 iovn.iov_base = (add == '\r' ? "\r\n" : "..");
177 iovn.iov_len = 2;
178 array_push_back(&iov_arr, &iovn);
179 i_assert(max_bytes >= 2);
180 max_bytes -= 2;
181 added++;
182 sent++;
183 }
184 }
185
186 if (max_bytes == 0)
187 break;
188 chunk = ((size_t)(p-data) >= max_bytes ?
189 max_bytes : (size_t)(p - data));
190 if (chunk > 0) {
191 iovn.iov_base = data;
192 iovn.iov_len = chunk;
193 array_push_back(&iov_arr, &iovn);
194 i_assert(max_bytes >= chunk);
195 max_bytes -= chunk;
196 sent += chunk;
197 }
198 }
199
200 /* send */
201 iov_new = array_get(&iov_arr, &count);
202 if (count == 0) {
203 ret = 0;
204 } else if ((ret=o_stream_sendv(stream->parent, iov_new, count)) <= 0) {
205 i_assert(ret < 0);
206 o_stream_copy_error_from_parent(stream);
207 return -1;
208 }
209
210 /* all must be sent */
211 i_assert((size_t)ret == sent + added);
212
213 stream->ostream.offset += sent;
214 return sent;
215 }
216
217 struct ostream *
o_stream_create_dot(struct ostream * output,bool force_extra_crlf)218 o_stream_create_dot(struct ostream *output, bool force_extra_crlf)
219 {
220 struct dot_ostream *dstream;
221
222 dstream = i_new(struct dot_ostream, 1);
223 dstream->ostream.sendv = o_stream_dot_sendv;
224 dstream->ostream.iostream.close = o_stream_dot_close;
225 dstream->ostream.flush = o_stream_dot_flush;
226 dstream->ostream.max_buffer_size = output->real_stream->max_buffer_size;
227 dstream->force_extra_crlf = force_extra_crlf;
228 (void)o_stream_create(&dstream->ostream, output, o_stream_get_fd(output));
229 /* ostream-dot is always used inside another ostream that shouldn't
230 get finished when the "." line is written. Disable it here so all
231 of the callers don't have to set this. */
232 o_stream_set_finish_also_parent(&dstream->ostream.ostream, FALSE);
233 return &dstream->ostream.ostream;
234 }
235