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