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
->endp
= &ch
[strlen(ch
)];
158 msgbuf
->n_para
= rb_string_to_array(ch
, (char **)msgbuf
->para
, MAXPARA
);
159 if (msgbuf
->n_para
== 0)
162 msgbuf
->cmd
= msgbuf
->para
[0];
167 * Unparse the tail of a msgbuf perfectly, preserving framing details
168 * msgbuf->para[n] will reach to the end of the line
172 msgbuf_reconstruct_tail(struct MsgBuf
*msgbuf
, size_t n
)
174 if (msgbuf
->endp
== NULL
|| n
> msgbuf
->n_para
)
181 c_
= msgbuf
->para
[n
];
183 c_
= msgbuf
->para
[n
-1] + strlen(msgbuf
->para
[n
-1]) + 1;
185 if (n
== msgbuf
->n_para
&& c_
== msgbuf
->endp
)
188 msgbuf
->para
[n
] = c_
;
189 /* promote to non-const. msgbuf->endp witnesses that this is allowed */
190 c
= msgbuf
->endp
- (msgbuf
->endp
- c_
);
192 for ( ; c
< msgbuf
->endp
; c
++)
201 * Unparse msgbuf tags into a buffer
202 * returns the length of the tags written
205 msgbuf_unparse_tags(char *buf
, size_t buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
207 bool has_tags
= false;
210 const char * const end
= &buf
[buflen
- 2]; /* this is where the final ' ' goes */
212 for (size_t i
= 0; i
< msgbuf
->n_tags
; i
++) {
215 if ((msgbuf
->tags
[i
].capmask
& capmask
) == 0)
228 if (msgbuf
->tags
[i
].key
== NULL
)
231 len
= strlen(msgbuf
->tags
[i
].key
);
235 if (output
+ len
> end
)
237 strcat(output
, msgbuf
->tags
[i
].key
);
240 if (msgbuf
->tags
[i
].value
!= NULL
) {
245 len
= strlen(msgbuf
->tags
[i
].value
);
246 /* this only checks the unescaped length,
247 * but the escaped length could be longer
249 if (output
+ len
> end
)
252 for (size_t n
= 0; n
< len
; n
++) {
253 const unsigned char c
= msgbuf
->tags
[i
].value
[n
];
254 const char escape
= tag_escape_table
[c
];
257 if (output
+ 2 > end
)
283 msgbuf_unparse_linebuf_tags(char *buf
, size_t buflen
, void *data
)
285 struct MsgBuf_str_data
*str_data
= data
;
287 return msgbuf_unparse_tags(buf
, buflen
, str_data
->msgbuf
, str_data
->caps
);
291 * unparse a MsgBuf and message prefix into a buffer
292 * if origin is NULL, me.name will be used.
293 * cmd should not be NULL.
294 * updates buflen to correctly allow remaining message data to be added
297 msgbuf_unparse_prefix(char *buf
, size_t *buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
303 memset(buf
, 0, *buflen
);
305 tags_buflen
= *buflen
;
306 if (tags_buflen
> TAGSLEN
+ 1)
307 tags_buflen
= TAGSLEN
+ 1;
309 if (msgbuf
->n_tags
> 0)
310 used
= msgbuf_unparse_tags(buf
, tags_buflen
, msgbuf
, capmask
);
312 const size_t data_bufmax
= (used
+ DATALEN
+ 1);
313 if (*buflen
> data_bufmax
)
314 *buflen
= data_bufmax
;
316 ret
= rb_snprintf_append(buf
, *buflen
, ":%s ", msgbuf
->origin
!= NULL
? msgbuf
->origin
: me
.name
);
320 if (msgbuf
->cmd
!= NULL
) {
321 ret
= rb_snprintf_append(buf
, *buflen
, "%s ", msgbuf
->cmd
);
326 if (msgbuf
->target
!= NULL
) {
327 ret
= rb_snprintf_append(buf
, *buflen
, "%s ", msgbuf
->target
);
332 if (used
> data_bufmax
- 1)
333 used
= data_bufmax
- 1;
339 * unparse a pure MsgBuf into a buffer.
340 * if origin is NULL, me.name will be used.
341 * cmd should not be NULL.
342 * returns 0 on success, 1 on error.
345 msgbuf_unparse(char *buf
, size_t buflen
, const struct MsgBuf
*msgbuf
, unsigned int capmask
)
347 size_t buflen_copy
= buflen
;
349 msgbuf_unparse_prefix(buf
, &buflen_copy
, msgbuf
, capmask
);
351 for (size_t i
= 0; i
< msgbuf
->n_para
; i
++) {
354 if (i
== (msgbuf
->n_para
- 1) && strchr(msgbuf
->para
[i
], ' ') != NULL
) {
355 fmt
= (i
== 0) ? ":%s" : " :%s";
357 fmt
= (i
== 0) ? "%s" : " %s";
360 rb_snprintf_append(buf
, buflen_copy
, fmt
, msgbuf
->para
[i
]);
367 * unparse a MsgBuf stem + format string into a buffer
368 * if origin is NULL, me.name will be used.
369 * cmd may not be NULL.
370 * returns 0 on success, 1 on error.
373 msgbuf_vunparse_fmt(char *buf
, size_t buflen
, const struct MsgBuf
*head
, unsigned int capmask
, const char *fmt
, va_list va
)
375 size_t buflen_copy
= buflen
;
379 msgbuf_unparse_prefix(buf
, &buflen_copy
, head
, capmask
);
380 prefixlen
= strlen(buf
);
382 ws
= buf
+ prefixlen
;
383 vsnprintf(ws
, buflen_copy
- prefixlen
, fmt
, va
);
389 * unparse a MsgBuf stem + format string into a buffer (with va_list handling)
390 * if origin is NULL, me.name will be used.
391 * cmd may not be NULL.
392 * returns 0 on success, 1 on error.
395 msgbuf_unparse_fmt(char *buf
, size_t buflen
, const struct MsgBuf
*head
, unsigned int capmask
, const char *fmt
, ...)
401 res
= msgbuf_vunparse_fmt(buf
, buflen
, head
, capmask
, fmt
, va
);
408 msgbuf_cache_init(struct MsgBuf_cache
*cache
, const struct MsgBuf
*msgbuf
, const rb_strf_t
*message
)
410 cache
->msgbuf
= msgbuf
;
412 cache
->overall_capmask
= 0;
414 for (size_t i
= 0; i
< msgbuf
->n_tags
; i
++) {
415 cache
->overall_capmask
|= msgbuf
->tags
[i
].capmask
;
418 for (int i
= 0; i
< MSGBUF_CACHE_SIZE
; i
++) {
419 cache
->entry
[i
].caps
= 0;
420 cache
->entry
[i
].next
= NULL
;
423 rb_fsnprint(cache
->message
, sizeof(cache
->message
), message
);
427 msgbuf_cache_initf(struct MsgBuf_cache
*cache
, const struct MsgBuf
*msgbuf
, const rb_strf_t
*message
, const char *format
, ...)
430 rb_strf_t strings
= { .format
= format
, .format_args
= &va
, .next
= message
};
432 va_start(va
, format
);
433 msgbuf_cache_init(cache
, msgbuf
, &strings
);
438 msgbuf_cache_get(struct MsgBuf_cache
*cache
, unsigned int caps
)
440 struct MsgBuf_cache_entry
*entry
= cache
->head
;
441 struct MsgBuf_cache_entry
*prev
= NULL
;
442 struct MsgBuf_cache_entry
*result
= NULL
;
445 while (entry
!= NULL
) {
446 if (entry
->caps
== caps
) {
457 if (result
== NULL
) {
458 if (n
< MSGBUF_CACHE_SIZE
) {
459 /* Cache miss, allocate a new entry */
460 result
= &cache
->entry
[n
];
463 /* Cache full, replace the last entry */
467 rb_linebuf_donebuf(&result
->linebuf
);
470 /* Construct the line using the tags followed by the no tags line */
471 struct MsgBuf_str_data msgbuf_str_data
= { .msgbuf
= cache
->msgbuf
, .caps
= caps
};
472 rb_strf_t strings
[2] = {
473 { .func
= msgbuf_unparse_linebuf_tags
, .func_args
= &msgbuf_str_data
, .length
= TAGSLEN
+ 1, .next
= &strings
[1] },
474 { .format
= cache
->message
, .length
= DATALEN
+ 1, .next
= NULL
}
478 rb_linebuf_newbuf(&result
->linebuf
);
479 rb_linebuf_put(&result
->linebuf
, &strings
[0]);
482 /* Move it to the top */
483 if (cache
->head
!= result
) {
485 prev
->next
= result
->next
;
487 result
->next
= cache
->head
;
488 cache
->head
= result
;
491 return &result
->linebuf
;
495 msgbuf_cache_free(struct MsgBuf_cache
*cache
)
497 struct MsgBuf_cache_entry
*entry
= cache
->head
;
499 while (entry
!= NULL
) {
500 rb_linebuf_donebuf(&entry
->linebuf
);