]> jfr.im git - solanum.git/blame - ircd/msgbuf.c
ircd: send tags on every message
[solanum.git] / ircd / msgbuf.c
CommitLineData
a8e69f5d
AC
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"
691adddd
AC
25#include "client.h"
26#include "ircd.h"
a8e69f5d 27
f3564f47
SA
28static 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
38static 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
50static void
51msgbuf_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;
05a16d98 69 in++;
f3564f47
SA
70 } else {
71 *out++ = *in++;
72 }
73 } else {
74 *out++ = *in++;
75 }
76 }
77
78 /* copy final '\0' */
79 *out = *in;
80}
81
a8e69f5d
AC
82/*
83 * parse a message into a MsgBuf.
84 * returns 0 on success, 1 on error.
85 */
86int
87msgbuf_parse(struct MsgBuf *msgbuf, char *line)
88{
f3564f47 89 char *ch = line;
a8e69f5d
AC
90
91 msgbuf_init(msgbuf);
92
f3564f47 93 if (*ch == '@') {
08006c16 94 char *t = ch + 1;
a8e69f5d
AC
95
96 ch = strchr(ch, ' ');
f3564f47
SA
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) {
a8e69f5d
AC
108 char *next = strchr(t, ';');
109 char *eq = strchr(t, '=');
110
f3564f47 111 if (next != NULL) {
a8e69f5d
AC
112 *next = '\0';
113
08006c16
AC
114 if (eq > next)
115 eq = NULL;
116 }
a8e69f5d
AC
117
118 if (eq != NULL)
08006c16 119 *eq++ = '\0';
a8e69f5d 120
f3564f47
SA
121 if (*t != '\0') {
122 msgbuf_unescape_value(eq);
d670fe52 123 msgbuf_append_tag(msgbuf, t, eq, 0);
f3564f47 124 }
269dd686 125
f3564f47 126 if (next != NULL) {
269dd686 127 t = next + 1;
f3564f47 128 } else {
269dd686 129 break;
f3564f47 130 }
a8e69f5d 131 }
f3564f47
SA
132 } else {
133 return 1;
a8e69f5d
AC
134 }
135 }
136
f3564f47
SA
137 /* truncate message if it's too long */
138 if (strlen(ch) > DATALEN) {
139 ch[DATALEN] = '\0';
140 }
a8e69f5d 141
f3564f47 142 if (*ch == ':') {
a8e69f5d
AC
143 ch++;
144 msgbuf->origin = ch;
145
146 char *end = strchr(ch, ' ');
147 if (end == NULL)
f3564f47 148 return 4;
a8e69f5d
AC
149
150 *end = '\0';
f3564f47 151 ch = end + 1;
a8e69f5d
AC
152 }
153
154 if (*ch == '\0')
f3564f47 155 return 2;
a8e69f5d 156
33ded5fc
SA
157 msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA);
158 if (msgbuf->n_para == 0)
f3564f47 159 return 3;
a8e69f5d 160
33ded5fc 161 msgbuf->cmd = msgbuf->para[0];
a8e69f5d
AC
162 return 0;
163}
4a13e3f1 164
f3564f47
SA
165/*
166 * Unparse msgbuf tags into a buffer
167 * returns the length of the tags written
168 */
169static size_t
170msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 171{
57dd2c6a 172 bool has_tags = false;
f3564f47
SA
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;
4a13e3f1 179
4a13e3f1
AC
180 if ((msgbuf->tags[i].capmask & capmask) == 0)
181 continue;
182
57dd2c6a 183 if (has_tags) {
f3564f47
SA
184 if (output >= end)
185 break;
186 *output++ = ';';
57dd2c6a 187 } else {
f3564f47
SA
188 if (output >= end)
189 break;
190 *output++ = '@';
57dd2c6a 191 }
4a13e3f1 192
f3564f47
SA
193 if (msgbuf->tags[i].key == NULL)
194 continue;
195
196 len = strlen(msgbuf->tags[i].key);
197 if (len == 0)
198 continue;
4a13e3f1 199
f3564f47
SA
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 }
4a13e3f1 234 }
f3564f47
SA
235
236 has_tags = true;
237 commit = output;
4a13e3f1
AC
238 }
239
57dd2c6a 240 if (has_tags)
f3564f47
SA
241 *commit++ = ' ';
242
243 *commit = 0;
244 return commit - buf;
4a13e3f1
AC
245}
246
4b1cce65
SA
247int
248msgbuf_unparse_linebuf_tags(char *buf, size_t buflen, void *data)
249{
250 struct MsgBuf_str_data *str_data = data;
251
252 return msgbuf_unparse_tags(buf, buflen, str_data->msgbuf, str_data->caps);
253}
254
f3564f47
SA
255/*
256 * unparse a MsgBuf and message prefix into a buffer
257 * if origin is NULL, me.name will be used.
258 * cmd should not be NULL.
259 * updates buflen to correctly allow remaining message data to be added
260 */
4b1cce65 261int
f3564f47 262msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 263{
f3564f47 264 size_t tags_buflen;
4b1cce65
SA
265 size_t used = 0;
266 int ret;
f3564f47
SA
267
268 memset(buf, 0, *buflen);
269
270 tags_buflen = *buflen;
271 if (tags_buflen > TAGSLEN + 1)
272 tags_buflen = TAGSLEN + 1;
4a13e3f1
AC
273
274 if (msgbuf->n_tags > 0)
4b1cce65 275 used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask);
f3564f47 276
4b1cce65 277 const size_t data_bufmax = (used + DATALEN + 1);
f3564f47
SA
278 if (*buflen > data_bufmax)
279 *buflen = data_bufmax;
4a13e3f1 280
4b1cce65
SA
281 ret = rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name);
282 if (ret > 0)
283 used += ret;
5559c3cf 284
4b1cce65
SA
285 if (msgbuf->cmd != NULL) {
286 ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd);
287 if (ret > 0)
288 used += ret;
289 }
290
291 if (msgbuf->target != NULL) {
292 ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target);
293 if (ret > 0)
294 used += ret;
295 }
296
297 if (used > data_bufmax - 1)
298 used = data_bufmax - 1;
71c875fb 299
4b1cce65 300 return used;
4a13e3f1
AC
301}
302
303/*
304 * unparse a pure MsgBuf into a buffer.
305 * if origin is NULL, me.name will be used.
f3564f47 306 * cmd should not be NULL.
4a13e3f1
AC
307 * returns 0 on success, 1 on error.
308 */
309int
f3564f47 310msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 311{
f3564f47
SA
312 size_t buflen_copy = buflen;
313
314 msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask);
315
316 for (size_t i = 0; i < msgbuf->n_para; i++) {
317 const char *fmt;
318
319 if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) {
320 fmt = (i == 0) ? ":%s" : " :%s";
321 } else {
322 fmt = (i == 0) ? "%s" : " %s";
4a13e3f1 323 }
f3564f47
SA
324
325 rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]);
4a13e3f1
AC
326 }
327
328 return 0;
329}
8f64d325
AC
330
331/*
332 * unparse a MsgBuf stem + format string into a buffer
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 */
337int
f3564f47 338msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va)
8f64d325 339{
f3564f47 340 size_t buflen_copy = buflen;
8f64d325
AC
341 char *ws;
342 size_t prefixlen;
343
f3564f47 344 msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask);
8f64d325
AC
345 prefixlen = strlen(buf);
346
347 ws = buf + prefixlen;
f3564f47 348 vsnprintf(ws, buflen_copy - prefixlen, fmt, va);
8f64d325
AC
349
350 return 0;
351}
352
353/*
354 * unparse a MsgBuf stem + format string into a buffer (with va_list handling)
355 * if origin is NULL, me.name will be used.
356 * cmd may not be NULL.
357 * returns 0 on success, 1 on error.
358 */
359int
f3564f47 360msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...)
8f64d325
AC
361{
362 va_list va;
363 int res;
364
365 va_start(va, fmt);
366 res = msgbuf_vunparse_fmt(buf, buflen, head, capmask, fmt, va);
367 va_end(va);
368
369 return res;
370}
4b1cce65
SA
371
372void
373msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message)
374{
375 cache->msgbuf = msgbuf;
376 cache->head = NULL;
377 cache->overall_capmask = 0;
378
379 for (size_t i = 0; i < msgbuf->n_tags; i++) {
380 cache->overall_capmask |= msgbuf->tags[i].capmask;
381 }
382
383 for (int i = 0; i < MSGBUF_CACHE_SIZE; i++) {
384 cache->entry[i].caps = 0;
385 cache->entry[i].next = NULL;
386 }
387
388 rb_fsnprint(cache->message, sizeof(cache->message), message);
389}
390
391void
392msgbuf_cache_initf(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message, const char *format, ...)
393{
394 va_list va;
395 rb_strf_t strings = { .format = format, .format_args = &va, .next = message };
396
397 va_start(va, format);
398 msgbuf_cache_init(cache, msgbuf, &strings);
399 va_end(va);
400}
401
402buf_head_t*
403msgbuf_cache_get(struct MsgBuf_cache *cache, unsigned int caps)
404{
405 struct MsgBuf_cache_entry *entry = cache->head;
406 struct MsgBuf_cache_entry *prev = NULL;
407 struct MsgBuf_cache_entry *result = NULL;
408 int n = 0;
409
410 while (entry != NULL) {
411 if (entry->caps == caps) {
412 /* Cache hit */
413 result = entry;
414 break;
415 }
416
417 prev = entry;
418 entry = entry->next;
419 n++;
420 }
421
422 if (result == NULL) {
423 if (n < MSGBUF_CACHE_SIZE) {
424 /* Cache miss, allocate a new entry */
425 result = &cache->entry[n];
426 prev = NULL;
427 } else {
428 /* Cache full, replace the last entry */
429 result = prev;
430 prev = NULL;
431
432 rb_linebuf_donebuf(&result->linebuf);
433 }
434
435 /* Construct the line using the tags followed by the no tags line */
436 struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = cache->msgbuf, .caps = caps };
437 rb_strf_t strings[2] = {
438 { .func = msgbuf_unparse_linebuf_tags, .func_args = &msgbuf_str_data, .length = TAGSLEN + 1, .next = &strings[1] },
439 { .format = cache->message, .length = DATALEN + 1, .next = NULL }
440 };
441
442 result->caps = caps;
443 rb_linebuf_newbuf(&result->linebuf);
444 rb_linebuf_put(&result->linebuf, &strings[0]);
445 }
446
447 /* Move it to the top */
448 if (cache->head != result) {
449 if (prev != NULL) {
450 prev->next = result->next;
451 }
452 result->next = cache->head;
453 cache->head = result;
454 }
455
456 return &result->linebuf;
457}
458
459void
460msgbuf_cache_free(struct MsgBuf_cache *cache)
461{
462 struct MsgBuf_cache_entry *entry = cache->head;
463
464 while (entry != NULL) {
465 rb_linebuf_donebuf(&entry->linebuf);
466 entry = entry->next;
467 }
468
469 cache->head = NULL;
470}