X-Git-Url: https://jfr.im/git/solanum.git/blobdiff_plain/d670fe5271c76714c3a85fdcb2d88eb483f84c14..e62ec6f17c37a80ac871663abfccae5f93754126:/ircd/msgbuf.c diff --git a/ircd/msgbuf.c b/ircd/msgbuf.c index a7fe53b2..a801ff62 100644 --- a/ircd/msgbuf.c +++ b/ircd/msgbuf.c @@ -1,6 +1,6 @@ /* - * charybdis - an advanced ircd. - * Copyright (c) 2016 William Pitcock . + * solanum - an advanced ircd. + * Copyright (c) 2016 Ariadne Conill . * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,6 +22,62 @@ #include "stdinc.h" #include "ircd_defs.h" #include "msgbuf.h" +#include "client.h" +#include "ircd.h" + +static const char tag_escape_table[256] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'n', 0, 0, 'r', 0, 0, + /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 2x */ 's', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ':', 0, 0, 0, 0, + /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, +}; + +static const char tag_unescape_table[256] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 2x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ';', 0, 0, 0, 0, 0, + /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + /* 6x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\n', 0, + /* 7x */ 0, 0,'\r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void +msgbuf_unescape_value(char *value) +{ + char *in = value; + char *out = value; + + if (value == NULL) + return; + + while (*in != '\0') { + if (*in == '\\') { + const char unescape = tag_unescape_table[(unsigned char)*++in]; + + /* "\\\0" is unescaped to the character itself, "\0" */ + if (*in == '\0') + break; + + if (unescape) { + *out++ = unescape; + in++; + } else { + *out++ = *in++; + } + } else { + *out++ = *in++; + } + } + + /* copy final '\0' */ + *out = *in; +} /* * parse a message into a MsgBuf. @@ -30,31 +86,29 @@ int msgbuf_parse(struct MsgBuf *msgbuf, char *line) { - char *ch; - char *parv[MAXPARA]; - size_t n_para; - int i; - - /* skip any leading spaces */ - for (ch = line; *ch && *ch == ' '; ch++) - ; + char *ch = line; msgbuf_init(msgbuf); - if (*ch == '@') - { + if (*ch == '@') { char *t = ch + 1; ch = strchr(ch, ' '); - if (ch != NULL) - { - while (1) - { + + /* truncate tags if they're too long */ + if ((ch != NULL && (ch - line) + 1 > TAGSLEN) || (ch == NULL && strlen(line) >= TAGSLEN)) { + ch = &line[TAGSLEN - 1]; + } + + if (ch != NULL) { + /* NULL terminate the tags string */ + *ch++ = '\0'; + + while (1) { char *next = strchr(t, ';'); char *eq = strchr(t, '='); - if (next != NULL) - { + if (next != NULL) { *next = '\0'; if (eq > next) @@ -64,50 +118,392 @@ msgbuf_parse(struct MsgBuf *msgbuf, char *line) if (eq != NULL) *eq++ = '\0'; - if (*t && *t != ' ') + if (*t != '\0') { + msgbuf_unescape_value(eq); msgbuf_append_tag(msgbuf, t, eq, 0); - else - break; + } - if (next != NULL) + if (next != NULL) { t = next + 1; - else + } else { break; + } } - - *ch++ = '\0'; + } else { + return 1; } } - /* skip any whitespace between tags and origin */ - for (; *ch && *ch == ' '; ch++) - ; + /* truncate message if it's too long */ + if (strlen(ch) > DATALEN) { + ch[DATALEN] = '\0'; + } - if (*ch == ':') - { + if (*ch == ':') { ch++; msgbuf->origin = ch; char *end = strchr(ch, ' '); if (end == NULL) - return 1; + return 4; *end = '\0'; - - for (ch = end + 1; *ch && *ch == ' '; ch++) - ; + ch = end + 1; } if (*ch == '\0') - return 1; + return 2; + + msgbuf->endp = &ch[strlen(ch)]; + msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA); + if (msgbuf->n_para == 0) + return 3; + + msgbuf->cmd = msgbuf->para[0]; + return 0; +} + +/* + * Unparse the tail of a msgbuf perfectly, preserving framing details + * msgbuf->para[n] will reach to the end of the line + */ + +void +msgbuf_reconstruct_tail(struct MsgBuf *msgbuf, size_t n) +{ + if (msgbuf->endp == NULL || n > msgbuf->n_para) + return; + + char *c; + const char *c_; + + if (n == 0) + c_ = msgbuf->para[n]; + else + c_ = msgbuf->para[n-1] + strlen(msgbuf->para[n-1]) + 1; + + if (n == msgbuf->n_para && c_ == msgbuf->endp) + return; + + msgbuf->para[n] = c_; + /* promote to non-const. msgbuf->endp witnesses that this is allowed */ + c = msgbuf->endp - (msgbuf->endp - c_); + + for ( ; c < msgbuf->endp; c++) + { + if (*c == '\0') + *c = ' '; + } +} + + +/* + * Unparse msgbuf tags into a buffer + * returns the length of the tags written + */ +static size_t +msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) +{ + bool has_tags = false; + char *commit = buf; + char *output = buf; + const char * const end = &buf[buflen - 2]; /* this is where the final ' ' goes */ + + for (size_t i = 0; i < msgbuf->n_tags; i++) { + size_t len; + + if ((msgbuf->tags[i].capmask & capmask) == 0) + continue; + + if (has_tags) { + if (output >= end) + break; + *output++ = ';'; + } else { + if (output >= end) + break; + *output++ = '@'; + } + + if (msgbuf->tags[i].key == NULL) + continue; + + len = strlen(msgbuf->tags[i].key); + if (len == 0) + continue; + + if (output + len > end) + break; + strcat(output, msgbuf->tags[i].key); + output += len; + + if (msgbuf->tags[i].value != NULL) { + if (output >= end) + break; + *output++ = '='; + + len = strlen(msgbuf->tags[i].value); + /* this only checks the unescaped length, + * but the escaped length could be longer + */ + if (output + len > end) + break; + + for (size_t n = 0; n < len; n++) { + const unsigned char c = msgbuf->tags[i].value[n]; + const char escape = tag_escape_table[c]; + + if (escape) { + if (output + 2 > end) + break; + + *output++ = '\\'; + *output++ = escape; + } else { + if (output >= end) + break; + + *output++ = c; + } + } + } + + has_tags = true; + commit = output; + } + + if (has_tags) + *commit++ = ' '; + + *commit = 0; + return commit - buf; +} + +int +msgbuf_unparse_linebuf_tags(char *buf, size_t buflen, void *data) +{ + struct MsgBuf_str_data *str_data = data; + + return msgbuf_unparse_tags(buf, buflen, str_data->msgbuf, str_data->caps); +} + +/* + * unparse a MsgBuf and message prefix into a buffer + * if origin is NULL, me.name will be used. + * cmd should not be NULL. + * updates buflen to correctly allow remaining message data to be added + */ +int +msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask) +{ + size_t tags_buflen; + size_t used = 0; + int ret; + + memset(buf, 0, *buflen); + + tags_buflen = *buflen; + if (tags_buflen > TAGSLEN + 1) + tags_buflen = TAGSLEN + 1; + + if (msgbuf->n_tags > 0) + used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask); + + const size_t data_bufmax = (used + DATALEN + 1); + if (*buflen > data_bufmax) + *buflen = data_bufmax; + + ret = rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name); + if (ret > 0) + used += ret; + + if (msgbuf->cmd != NULL) { + ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd); + if (ret > 0) + used += ret; + } + + if (msgbuf->target != NULL) { + ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target); + if (ret > 0) + used += ret; + } + + if (used > data_bufmax - 1) + used = data_bufmax - 1; + + return used; +} + +/* + * unparse a pure MsgBuf into a buffer. + * if origin is NULL, me.name will be used. + * cmd should not be NULL. + * returns 0 on success, 1 on error. + */ +int +msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) +{ + size_t buflen_copy = buflen; + + msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask); + + for (size_t i = 0; i < msgbuf->n_para; i++) { + const char *fmt; + + if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) { + fmt = (i == 0) ? ":%s" : " :%s"; + } else { + fmt = (i == 0) ? "%s" : " %s"; + } + + rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]); + } + + return 0; +} - n_para = rb_string_to_array(ch, parv, MAXPARA); - if (n_para == 0) - return 1; +/* + * unparse a MsgBuf stem + format string into a buffer + * if origin is NULL, me.name will be used. + * cmd may not be NULL. + * returns 0 on success, 1 on error. + */ +int +msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va) +{ + size_t buflen_copy = buflen; + char *ws; + size_t prefixlen; - msgbuf->cmd = parv[0]; - for (i = 0; i < n_para; i++) - msgbuf_append_para(msgbuf, parv[i]); + msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask); + prefixlen = strlen(buf); + + ws = buf + prefixlen; + vsnprintf(ws, buflen_copy - prefixlen, fmt, va); return 0; } + +/* + * unparse a MsgBuf stem + format string into a buffer (with va_list handling) + * if origin is NULL, me.name will be used. + * cmd may not be NULL. + * returns 0 on success, 1 on error. + */ +int +msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) +{ + va_list va; + int res; + + va_start(va, fmt); + res = msgbuf_vunparse_fmt(buf, buflen, head, capmask, fmt, va); + va_end(va); + + return res; +} + +void +msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message) +{ + cache->msgbuf = msgbuf; + cache->head = NULL; + cache->overall_capmask = 0; + + for (size_t i = 0; i < msgbuf->n_tags; i++) { + cache->overall_capmask |= msgbuf->tags[i].capmask; + } + + for (int i = 0; i < MSGBUF_CACHE_SIZE; i++) { + cache->entry[i].caps = 0; + cache->entry[i].next = NULL; + } + + rb_fsnprint(cache->message, sizeof(cache->message), message); +} + +void +msgbuf_cache_initf(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message, const char *format, ...) +{ + va_list va; + rb_strf_t strings = { .format = format, .format_args = &va, .next = message }; + + va_start(va, format); + msgbuf_cache_init(cache, msgbuf, &strings); + va_end(va); +} + +buf_head_t* +msgbuf_cache_get(struct MsgBuf_cache *cache, unsigned int caps) +{ + struct MsgBuf_cache_entry *entry = cache->head; + struct MsgBuf_cache_entry *prev = NULL; + struct MsgBuf_cache_entry *result = NULL; + struct MsgBuf_cache_entry *tail = NULL; + int n = 0; + + while (entry != NULL) { + if (entry->caps == caps) { + /* Cache hit */ + result = entry; + break; + } + + tail = prev; + prev = entry; + entry = entry->next; + n++; + } + + if (result == NULL) { + if (n < MSGBUF_CACHE_SIZE) { + /* Cache miss, allocate a new entry */ + result = &cache->entry[n]; + prev = NULL; + } else { + /* Cache full, replace the last entry */ + result = prev; + if (tail != NULL) + tail->next = NULL; + prev = NULL; + + rb_linebuf_donebuf(&result->linebuf); + } + + /* Construct the line using the tags followed by the no tags line */ + struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = cache->msgbuf, .caps = caps }; + rb_strf_t strings[2] = { + { .func = msgbuf_unparse_linebuf_tags, .func_args = &msgbuf_str_data, .length = TAGSLEN + 1, .next = &strings[1] }, + { .format = cache->message, .length = DATALEN + 1, .next = NULL } + }; + + result->caps = caps; + rb_linebuf_newbuf(&result->linebuf); + rb_linebuf_put(&result->linebuf, &strings[0]); + } + + /* Move it to the top */ + if (cache->head != result) { + if (prev != NULL) { + prev->next = result->next; + } + result->next = cache->head; + cache->head = result; + } + + return &result->linebuf; +} + +void +msgbuf_cache_free(struct MsgBuf_cache *cache) +{ + struct MsgBuf_cache_entry *entry = cache->head; + + while (entry != NULL) { + rb_linebuf_donebuf(&entry->linebuf); + entry = entry->next; + } + + cache->head = NULL; +}