2 * charybdis - an advanced ircd.
3 * Copyright (c) 2016 William Pitcock <nenolod@dereferenced.org>.
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.
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.
23 #include "ircd_defs.h"
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,
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,
51 msgbuf_unescape_value(char *value
)
61 const char unescape
= tag_unescape_table
[(unsigned char)*++in
];
63 /* "\\\0" is unescaped to the character itself, "\0" */
83 * parse a message into a MsgBuf.
84 * returns 0 on success, 1 on error.
87 msgbuf_parse(struct MsgBuf
*msgbuf
, char *line
)
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];
104 /* NULL terminate the tags string */
108 char *next
= strchr(t
, ';');
109 char *eq
= strchr(t
, '=');
122 msgbuf_unescape_value(eq
);
123 msgbuf_append_tag(msgbuf
, t
, eq
, 0);
137 /* truncate message if it's too long */
138 if (strlen(ch
) > DATALEN
) {
146 char *end
= strchr(ch
, ' ');
157 msgbuf
->n_para
= rb_string_to_array(ch
, (char **)msgbuf
->para
, MAXPARA
);
158 if (msgbuf
->n_para
== 0)
161 msgbuf
->cmd
= msgbuf
->para
[0];
166 * Unparse msgbuf tags into a buffer
167 * returns the length of the tags written
170 msgbuf_unparse_tags(char *buf
, size_t buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
172 bool has_tags
= false;
175 const char * const end
= &buf
[buflen
- 2]; /* this is where the final ' ' goes */
177 for (size_t i
= 0; i
< msgbuf
->n_tags
; i
++) {
180 if ((msgbuf
->tags
[i
].capmask
& capmask
) == 0)
193 if (msgbuf
->tags
[i
].key
== NULL
)
196 len
= strlen(msgbuf
->tags
[i
].key
);
200 if (output
+ len
> end
)
202 strcat(output
, msgbuf
->tags
[i
].key
);
205 if (msgbuf
->tags
[i
].value
!= NULL
) {
210 len
= strlen(msgbuf
->tags
[i
].value
);
211 /* this only checks the unescaped length,
212 * but the escaped length could be longer
214 if (output
+ len
> end
)
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
];
222 if (output
+ 2 > end
)
248 msgbuf_unparse_linebuf_tags(char *buf
, size_t buflen
, void *data
)
250 struct MsgBuf_str_data
*str_data
= data
;
252 return msgbuf_unparse_tags(buf
, buflen
, str_data
->msgbuf
, str_data
->caps
);
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
262 msgbuf_unparse_prefix(char *buf
, size_t *buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
268 memset(buf
, 0, *buflen
);
270 tags_buflen
= *buflen
;
271 if (tags_buflen
> TAGSLEN
+ 1)
272 tags_buflen
= TAGSLEN
+ 1;
274 if (msgbuf
->n_tags
> 0)
275 used
= msgbuf_unparse_tags(buf
, tags_buflen
, msgbuf
, capmask
);
277 const size_t data_bufmax
= (used
+ DATALEN
+ 1);
278 if (*buflen
> data_bufmax
)
279 *buflen
= data_bufmax
;
281 ret
= rb_snprintf_append(buf
, *buflen
, ":%s ", msgbuf
->origin
!= NULL
? msgbuf
->origin
: me
.name
);
285 if (msgbuf
->cmd
!= NULL
) {
286 ret
= rb_snprintf_append(buf
, *buflen
, "%s ", msgbuf
->cmd
);
291 if (msgbuf
->target
!= NULL
) {
292 ret
= rb_snprintf_append(buf
, *buflen
, "%s ", msgbuf
->target
);
297 if (used
> data_bufmax
- 1)
298 used
= data_bufmax
- 1;
304 * unparse a pure MsgBuf into a buffer.
305 * if origin is NULL, me.name will be used.
306 * cmd should not be NULL.
307 * returns 0 on success, 1 on error.
310 msgbuf_unparse(char *buf
, size_t buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
312 size_t buflen_copy
= buflen
;
314 msgbuf_unparse_prefix(buf
, &buflen_copy
, msgbuf
, capmask
);
316 for (size_t i
= 0; i
< msgbuf
->n_para
; i
++) {
319 if (i
== (msgbuf
->n_para
- 1) && strchr(msgbuf
->para
[i
], ' ') != NULL
) {
320 fmt
= (i
== 0) ? ":%s" : " :%s";
322 fmt
= (i
== 0) ? "%s" : " %s";
325 rb_snprintf_append(buf
, buflen_copy
, fmt
, msgbuf
->para
[i
]);
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.
338 msgbuf_vunparse_fmt(char *buf
, size_t buflen
, const struct MsgBuf
*head
, unsigned int capmask
, const char *fmt
, va_list va
)
340 size_t buflen_copy
= buflen
;
344 msgbuf_unparse_prefix(buf
, &buflen_copy
, head
, capmask
);
345 prefixlen
= strlen(buf
);
347 ws
= buf
+ prefixlen
;
348 vsnprintf(ws
, buflen_copy
- prefixlen
, fmt
, va
);
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.
360 msgbuf_unparse_fmt(char *buf
, size_t buflen
, const struct MsgBuf
*head
, unsigned int capmask
, const char *fmt
, ...)
366 res
= msgbuf_vunparse_fmt(buf
, buflen
, head
, capmask
, fmt
, va
);
373 msgbuf_cache_init(struct MsgBuf_cache
*cache
, const struct MsgBuf
*msgbuf
, const rb_strf_t
*message
)
375 cache
->msgbuf
= msgbuf
;
377 cache
->overall_capmask
= 0;
379 for (size_t i
= 0; i
< msgbuf
->n_tags
; i
++) {
380 cache
->overall_capmask
|= msgbuf
->tags
[i
].capmask
;
383 for (int i
= 0; i
< MSGBUF_CACHE_SIZE
; i
++) {
384 cache
->entry
[i
].caps
= 0;
385 cache
->entry
[i
].next
= NULL
;
388 rb_fsnprint(cache
->message
, sizeof(cache
->message
), message
);
392 msgbuf_cache_initf(struct MsgBuf_cache
*cache
, const struct MsgBuf
*msgbuf
, const rb_strf_t
*message
, const char *format
, ...)
395 rb_strf_t strings
= { .format
= format
, .format_args
= &va
, .next
= message
};
397 va_start(va
, format
);
398 msgbuf_cache_init(cache
, msgbuf
, &strings
);
403 msgbuf_cache_get(struct MsgBuf_cache
*cache
, unsigned int caps
)
405 struct MsgBuf_cache_entry
*entry
= cache
->head
;
406 struct MsgBuf_cache_entry
*prev
= NULL
;
407 struct MsgBuf_cache_entry
*result
= NULL
;
410 while (entry
!= NULL
) {
411 if (entry
->caps
== caps
) {
422 if (result
== NULL
) {
423 if (n
< MSGBUF_CACHE_SIZE
) {
424 /* Cache miss, allocate a new entry */
425 result
= &cache
->entry
[n
];
428 /* Cache full, replace the last entry */
432 rb_linebuf_donebuf(&result
->linebuf
);
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
}
443 rb_linebuf_newbuf(&result
->linebuf
);
444 rb_linebuf_put(&result
->linebuf
, &strings
[0]);
447 /* Move it to the top */
448 if (cache
->head
!= result
) {
450 prev
->next
= result
->next
;
452 result
->next
= cache
->head
;
453 cache
->head
= result
;
456 return &result
->linebuf
;
460 msgbuf_cache_free(struct MsgBuf_cache
*cache
)
462 struct MsgBuf_cache_entry
*entry
= cache
->head
;
464 while (entry
!= NULL
) {
465 rb_linebuf_donebuf(&entry
->linebuf
);