]> jfr.im git - solanum.git/blob - ircd/msgbuf.c
librb: linebuf: reduce the number of "put" implementations from 4 to 1
[solanum.git] / ircd / msgbuf.c
1 /*
2 * charybdis - an advanced ircd.
3 * Copyright (c) 2016 William Pitcock <nenolod@dereferenced.org>.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice is present in all copies.
8 *
9 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
10 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
13 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
14 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
15 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
16 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
18 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
19 * POSSIBILITY OF SUCH DAMAGE.
20 */
21
22 #include "stdinc.h"
23 #include "ircd_defs.h"
24 #include "msgbuf.h"
25 #include "client.h"
26 #include "ircd.h"
27
28 static const char tag_escape_table[256] = {
29 /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
30 /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'n', 0, 0, 'r', 0, 0,
31 /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
32 /* 2x */ 's', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33 /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ':', 0, 0, 0, 0,
34 /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35 /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0,
36 };
37
38 static const char tag_unescape_table[256] = {
39 /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
40 /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41 /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42 /* 2x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
43 /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ';', 0, 0, 0, 0, 0,
44 /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
45 /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0,
46 /* 6x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\n', 0,
47 /* 7x */ 0, 0,'\r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
48 };
49
50 static void
51 msgbuf_unescape_value(char *value)
52 {
53 char *in = value;
54 char *out = value;
55
56 if (value == NULL)
57 return;
58
59 while (*in != '\0') {
60 if (*in == '\\') {
61 const char unescape = tag_unescape_table[(unsigned char)*++in];
62
63 /* "\\\0" is unescaped to the character itself, "\0" */
64 if (*in == '\0')
65 break;
66
67 if (unescape) {
68 *out++ = unescape;
69 in++;
70 } else {
71 *out++ = *in++;
72 }
73 } else {
74 *out++ = *in++;
75 }
76 }
77
78 /* copy final '\0' */
79 *out = *in;
80 }
81
82 /*
83 * parse a message into a MsgBuf.
84 * returns 0 on success, 1 on error.
85 */
86 int
87 msgbuf_parse(struct MsgBuf *msgbuf, char *line)
88 {
89 char *ch = line;
90
91 msgbuf_init(msgbuf);
92
93 if (*ch == '@') {
94 char *t = ch + 1;
95
96 ch = strchr(ch, ' ');
97
98 /* truncate tags if they're too long */
99 if ((ch != NULL && (ch - line) + 1 > TAGSLEN) || (ch == NULL && strlen(line) >= TAGSLEN)) {
100 ch = &line[TAGSLEN - 1];
101 }
102
103 if (ch != NULL) {
104 /* NULL terminate the tags string */
105 *ch++ = '\0';
106
107 while (1) {
108 char *next = strchr(t, ';');
109 char *eq = strchr(t, '=');
110
111 if (next != NULL) {
112 *next = '\0';
113
114 if (eq > next)
115 eq = NULL;
116 }
117
118 if (eq != NULL)
119 *eq++ = '\0';
120
121 if (*t != '\0') {
122 msgbuf_unescape_value(eq);
123 msgbuf_append_tag(msgbuf, t, eq, 0);
124 }
125
126 if (next != NULL) {
127 t = next + 1;
128 } else {
129 break;
130 }
131 }
132 } else {
133 return 1;
134 }
135 }
136
137 /* truncate message if it's too long */
138 if (strlen(ch) > DATALEN) {
139 ch[DATALEN] = '\0';
140 }
141
142 if (*ch == ':') {
143 ch++;
144 msgbuf->origin = ch;
145
146 char *end = strchr(ch, ' ');
147 if (end == NULL)
148 return 4;
149
150 *end = '\0';
151 ch = end + 1;
152 }
153
154 if (*ch == '\0')
155 return 2;
156
157 msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA);
158 if (msgbuf->n_para == 0)
159 return 3;
160
161 msgbuf->cmd = msgbuf->para[0];
162 return 0;
163 }
164
165 /*
166 * Unparse msgbuf tags into a buffer
167 * returns the length of the tags written
168 */
169 static size_t
170 msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
171 {
172 bool has_tags = false;
173 char *commit = buf;
174 char *output = buf;
175 const char * const end = &buf[buflen - 2]; /* this is where the final ' ' goes */
176
177 for (size_t i = 0; i < msgbuf->n_tags; i++) {
178 size_t len;
179
180 if ((msgbuf->tags[i].capmask & capmask) == 0)
181 continue;
182
183 if (has_tags) {
184 if (output >= end)
185 break;
186 *output++ = ';';
187 } else {
188 if (output >= end)
189 break;
190 *output++ = '@';
191 }
192
193 if (msgbuf->tags[i].key == NULL)
194 continue;
195
196 len = strlen(msgbuf->tags[i].key);
197 if (len == 0)
198 continue;
199
200 if (output + len > end)
201 break;
202 strcat(output, msgbuf->tags[i].key);
203 output += len;
204
205 if (msgbuf->tags[i].value != NULL) {
206 if (output >= end)
207 break;
208 *output++ = '=';
209
210 len = strlen(msgbuf->tags[i].value);
211 /* this only checks the unescaped length,
212 * but the escaped length could be longer
213 */
214 if (output + len > end)
215 break;
216
217 for (size_t n = 0; n < len; n++) {
218 const unsigned char c = msgbuf->tags[i].value[n];
219 const char escape = tag_escape_table[c];
220
221 if (escape) {
222 if (output + 2 > end)
223 break;
224
225 *output++ = '\\';
226 *output++ = escape;
227 } else {
228 if (output >= end)
229 break;
230
231 *output++ = c;
232 }
233 }
234 }
235
236 has_tags = true;
237 commit = output;
238 }
239
240 if (has_tags)
241 *commit++ = ' ';
242
243 *commit = 0;
244 return commit - buf;
245 }
246
247 /*
248 * unparse a MsgBuf and message prefix into a buffer
249 * if origin is NULL, me.name will be used.
250 * cmd should not be NULL.
251 * updates buflen to correctly allow remaining message data to be added
252 */
253 void
254 msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
255 {
256 size_t tags_buflen;
257 size_t tags_used = 0;
258
259 memset(buf, 0, *buflen);
260
261 tags_buflen = *buflen;
262 if (tags_buflen > TAGSLEN + 1)
263 tags_buflen = TAGSLEN + 1;
264
265 if (msgbuf->n_tags > 0)
266 tags_used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask);
267
268 const size_t data_bufmax = (tags_used + DATALEN + 1);
269 if (*buflen > data_bufmax)
270 *buflen = data_bufmax;
271
272 rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name);
273
274 if (msgbuf->cmd != NULL)
275 rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd);
276
277 if (msgbuf->target != NULL)
278 rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target);
279 }
280
281 /*
282 * unparse a pure MsgBuf into a buffer.
283 * if origin is NULL, me.name will be used.
284 * cmd should not be NULL.
285 * returns 0 on success, 1 on error.
286 */
287 int
288 msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
289 {
290 size_t buflen_copy = buflen;
291
292 msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask);
293
294 for (size_t i = 0; i < msgbuf->n_para; i++) {
295 const char *fmt;
296
297 if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) {
298 fmt = (i == 0) ? ":%s" : " :%s";
299 } else {
300 fmt = (i == 0) ? "%s" : " %s";
301 }
302
303 rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]);
304 }
305
306 return 0;
307 }
308
309 /*
310 * unparse a MsgBuf stem + format string into a buffer
311 * if origin is NULL, me.name will be used.
312 * cmd may not be NULL.
313 * returns 0 on success, 1 on error.
314 */
315 int
316 msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va)
317 {
318 size_t buflen_copy = buflen;
319 char *ws;
320 size_t prefixlen;
321
322 msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask);
323 prefixlen = strlen(buf);
324
325 ws = buf + prefixlen;
326 vsnprintf(ws, buflen_copy - prefixlen, fmt, va);
327
328 return 0;
329 }
330
331 /*
332 * unparse a MsgBuf stem + format string into a buffer (with va_list handling)
333 * if origin is NULL, me.name will be used.
334 * cmd may not be NULL.
335 * returns 0 on success, 1 on error.
336 */
337 int
338 msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...)
339 {
340 va_list va;
341 int res;
342
343 va_start(va, fmt);
344 res = msgbuf_vunparse_fmt(buf, buflen, head, capmask, fmt, va);
345 va_end(va);
346
347 return res;
348 }