From fa27329dc4517defc5c0e05881198a26e97cb3a4 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 30 Aug 2021 22:26:41 +0200 Subject: [PATCH] Add support for message-tags Not doing anything with it yet, though. --- src/core/app_irc_event.c | 8 +++ src/core/server_cap.c | 4 ++ src/inc/core/server.h | 1 + src/inc/sirc/sirc_event.h | 1 + src/sirc/sirc_event_hdr.c | 4 ++ src/sirc/sirc_parse.c | 111 +++++++++++++++++++++++++++++++++++--- src/sirc/sirc_parse.h | 8 +++ 7 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/core/app_irc_event.c b/src/core/app_irc_event.c index 65f4b82a..5160c7e7 100644 --- a/src/core/app_irc_event.c +++ b/src/core/app_irc_event.c @@ -73,6 +73,8 @@ static void irc_event_privmsg(SircSession *sirc, const char *event, const char *origin, const char *params[], int count); static void irc_event_notice(SircSession *sirc, const char *event, const char *origin, const char *params[], int count); +static void irc_event_tagmsg(SircSession *sirc, const char *event, + const char *origin, const char *params[], int count); static void irc_event_channel_notice(SircSession *sirc, const char *event, const char *origin, const char *params[], int count); static void irc_event_invite(SircSession *sirc, const char *event, @@ -113,6 +115,7 @@ void srn_application_init_irc_event(SrnApplication *app) { app->irc_events.channel = irc_event_channel; app->irc_events.privmsg = irc_event_privmsg; app->irc_events.notice = irc_event_notice; + app->irc_events.tagmsg = irc_event_tagmsg; app->irc_events.channel_notice = irc_event_channel_notice; app->irc_events.invite = irc_event_invite; app->irc_events.ctcp_req = irc_event_ctcp_req; @@ -824,6 +827,11 @@ static void irc_event_notice(SircSession *sirc, const char *event, srn_chat_add_notice_message(chat, chat_user, msg); } +static void irc_event_tagmsg(SircSession *sirc, const char *event, + const char *origin, const char **params, int count){ + /* Not used yet */ +} + static void irc_event_channel_notice(SircSession *sirc, const char *event, const char *origin, const char **params, int count){ const char *chan; diff --git a/src/core/server_cap.c b/src/core/server_cap.c index 8917f552..d4d4c606 100644 --- a/src/core/server_cap.c +++ b/src/core/server_cap.c @@ -80,6 +80,10 @@ static ServerCapSupport supported_caps[] = { }, // /* IRCv3.2 */ + { + .name = "message-tags", + .offset = offsetof(EnabledCap, message_tags), + }, // { // .name = "server-time", // .offset = offsetof(EnabledCap, server_time), diff --git a/src/inc/core/server.h b/src/inc/core/server.h index 29d1761e..cb050c11 100644 --- a/src/inc/core/server.h +++ b/src/inc/core/server.h @@ -180,6 +180,7 @@ struct _EnabledCap { bool sasl; // Version 3.2 + bool message_tags; bool server_time; bool userhost_in_names; bool cap_notify; diff --git a/src/inc/sirc/sirc_event.h b/src/inc/sirc/sirc_event.h index bc5f8134..6d4ddd9f 100644 --- a/src/inc/sirc/sirc_event.h +++ b/src/inc/sirc/sirc_event.h @@ -47,6 +47,7 @@ typedef struct { SircEventCallback channel; SircEventCallback privmsg; SircEventCallback notice; + SircEventCallback tagmsg; SircEventCallback channel_notice; SircEventCallback invite; SircEventCallback ctcp_req; diff --git a/src/sirc/sirc_event_hdr.c b/src/sirc/sirc_event_hdr.c index c71addbf..ea9caecd 100644 --- a/src/sirc/sirc_event_hdr.c +++ b/src/sirc/sirc_event_hdr.c @@ -190,6 +190,10 @@ void sirc_event_hdr(SircSession *sirc, SircMessage *imsg){ g_return_if_fail(events->error); events->error(sirc, event, origin, params, imsg->nparam); } + else if (strcasecmp(event, "TAGMSG") == 0){ + g_return_if_fail(events->error); + events->tagmsg(sirc, event, origin, params, imsg->nparam); + } else { g_return_if_fail(events->unknown); events->unknown(sirc, event, origin, params, imsg->nparam); diff --git a/src/sirc/sirc_parse.c b/src/sirc/sirc_parse.c index bc86f81f..1b543df5 100644 --- a/src/sirc/sirc_parse.c +++ b/src/sirc/sirc_parse.c @@ -35,6 +35,9 @@ #include "log.h" #include "utils.h" +/* https://ircv3.net/specs/extensions/message-tags#size-limit */ +#define TAGS_SIZE_LIMIT 8191 + SircMessage *sirc_message_new(){ return g_malloc0(sizeof(SircMessage)); } @@ -50,6 +53,15 @@ void sirc_message_free(SircMessage *imsg){ str_assign(&imsg->params[i], NULL); } + for (int i = 0; i < imsg->ntags; i++){ + str_assign(&imsg->tags[i].key, NULL); + str_assign(&imsg->tags[i].value, NULL); + } + + if (imsg->ntags > 0) { + g_free(imsg->tags); + } + g_free(imsg); } @@ -63,6 +75,8 @@ void sirc_message_transcoding(SircMessage *imsg, const char *from_codeset) { for (int i = 0; i < imsg->nparam; i++){ str_transcoding(&imsg->params[i], from_codeset); } + + /* No need to transcode tags, they are guaranteed to be UTF-8 */ } /** @@ -80,20 +94,105 @@ SircMessage* sirc_parse(char *line){ imsg = sirc_message_new(); /* This is a IRC message - * IRS protocol message format? - * See: https://tools.ietf.org/html/rfc1459#section-2.3 + * IRC protocol message format? + * See: https://ircv3.net/specs/extensions/message-tags */ + char *rfc1459_line; + char *tags_ptr; char *prefix_ptr, *command_ptr; char *trailing_ptr, *params_ptr; char *nick_ptr, *user_ptr, *host_ptr; - // ::= [':' ] - if (line[0] == ':'){ - prefix_ptr = strtok(line + 1, " "); // Skip ':' + // ::= ['@' ] [':' ] + if (line[0] == '@'){ + size_t ntags; + tags_ptr = line + 1; + rfc1459_line = strchr(line, ' ') + 1; + + /* Count the number of tags to allocate a tag array */ + ntags = 1; + for (char *p=tags_ptr; *p != ' '; p++){ + if (*p == ';'){ + ntags++; + } + } + + imsg->ntags = ntags; + imsg->tags = g_malloc_n(ntags, sizeof(SircMessageTag)); + size_t i=0; + char current_tag_key[TAGS_SIZE_LIMIT]; + char current_tag_value[TAGS_SIZE_LIMIT]; + char *current_tag_key_ptr = current_tag_key; + char *current_tag_value_ptr = current_tag_value; + gboolean in_key = TRUE; + for (char *p=tags_ptr; ; p++){ + if (*p == ';' || *p == ' '){ + /* next tag or end of tags*/ + in_key = TRUE; + *current_tag_key_ptr = '\0'; + imsg->tags[i].key = strdup(current_tag_key); + if (current_tag_value == current_tag_value_ptr){ + /* Key is absent or empty */ + imsg->tags[i].value = NULL; + } + else { + *current_tag_value_ptr = '\0'; + imsg->tags[i].value = strdup(current_tag_value); + } + if (*p == ' '){ + /* end of tags */ + break; + } + i++; + + current_tag_key_ptr = current_tag_key; + current_tag_value_ptr = current_tag_value; + } + else if (*p == '=' && in_key){ + /* tag's value */ + in_key = FALSE; + } + else if (in_key){ + *(current_tag_key_ptr++) = *p; + } + else if (!in_key && *p == '\\'){ + /* Possibly an escaped character in the value */ + p++; + if (*p == ':') + *(current_tag_value_ptr++) = ';'; + else if (*p == 's') + *(current_tag_value_ptr++) = ' '; + else if (*p == '\\') + *(current_tag_value_ptr++) = '\\'; + else if (*p == 'r') + *(current_tag_value_ptr++) = '\r'; + else if (*p == 'n') + *(current_tag_value_ptr++) = '\n'; + else{ + /* Not an escaped character, it was a literal \ */ + *(current_tag_value_ptr++) = '\\'; + *(current_tag_value_ptr++) = *p; + } + } + else{ + *(current_tag_value_ptr++) = *p; + } + } + } + else{ + imsg->tags = NULL; + imsg->ntags = 0; + rfc1459_line = line; + } + + /* Now parse like in RFC1459 */ + + if (rfc1459_line[0] == ':'){ + prefix_ptr = strtok(rfc1459_line + 1, " "); // Skip ':' command_ptr = strtok(NULL, " "); } else { prefix_ptr = NULL; - command_ptr = strtok(line, " "); + command_ptr = strtok(rfc1459_line, " "); } params_ptr = strtok(NULL, ""); diff --git a/src/sirc/sirc_parse.h b/src/sirc/sirc_parse.h index aa4b7b33..4f8f683d 100644 --- a/src/sirc/sirc_parse.h +++ b/src/sirc/sirc_parse.h @@ -22,6 +22,14 @@ #define SIRC_PARAM_COUNT 64 // RFC 2812 limits it to 14 typedef struct { + char *key; + char *value; // possibly NULL +} SircMessageTag; + +typedef struct { + size_t ntags; + SircMessageTag *tags; + char *prefix; // servername or nick!user@host char *nick, *user, *host;