summaryrefslogtreecommitdiff
path: root/src/plugins/irc/irc-tag.c
diff options
context:
space:
mode:
authorSébastien Helleu <flashcode@flashtux.org>2021-06-21 21:26:45 +0200
committerSébastien Helleu <flashcode@flashtux.org>2021-06-24 20:59:21 +0200
commit23c46c3f2bfa735d30e815b9ff45c8008adbcbc5 (patch)
tree48e7f338dd1ceb40df928b3558110ab25512c394 /src/plugins/irc/irc-tag.c
parentb3b4ef648b0a858c4183dba28071b2c84ef31a7c (diff)
downloadweechat-23c46c3f2bfa735d30e815b9ff45c8008adbcbc5.zip
irc: escape/unescape IRC message tags values (issue #1654)
Spec: https://ircv3.net/specs/extensions/message-tags#escaping-values
Diffstat (limited to 'src/plugins/irc/irc-tag.c')
-rw-r--r--src/plugins/irc/irc-tag.c280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/plugins/irc/irc-tag.c b/src/plugins/irc/irc-tag.c
new file mode 100644
index 000000000..a0534e0ad
--- /dev/null
+++ b/src/plugins/irc/irc-tag.c
@@ -0,0 +1,280 @@
+/*
+ * irc-tag.c - functions for IRC message tags
+ *
+ * Copyright (C) 2021 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WeeChat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "../weechat-plugin.h"
+#include "irc.h"
+#include "irc-tag.h"
+
+
+/*
+ * Escapes a tag value, the following sequences are replaced:
+ *
+ * character | escaped value
+ * ---------------+-------------------------
+ * ; (semicolon) | \: (backslash and colon)
+ * SPACE | \s
+ * \ | \\
+ * CR | \r
+ * LF | \n
+ * all others | the character itself
+ *
+ * See: https://ircv3.net/specs/extensions/message-tags#escaping-values
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+irc_tag_escape_value (const char *string)
+{
+ char **out, *result;
+ unsigned char *ptr_string;
+ int length;
+
+ if (!string)
+ return NULL;
+
+ length = strlen (string);
+ out = weechat_string_dyn_alloc (length + (length / 2) + 1);
+ if (!out)
+ return NULL;
+
+ ptr_string = (unsigned char *)string;
+ while (ptr_string && ptr_string[0])
+ {
+ switch (ptr_string[0])
+ {
+ case ';':
+ weechat_string_dyn_concat (out, "\\:", -1);
+ ptr_string++;
+ break;
+ case ' ':
+ weechat_string_dyn_concat (out, "\\s", -1);
+ ptr_string++;
+ break;
+ case '\\':
+ weechat_string_dyn_concat (out, "\\\\", -1);
+ ptr_string++;
+ break;
+ case '\r':
+ weechat_string_dyn_concat (out, "\\r", -1);
+ ptr_string++;
+ break;
+ case '\n':
+ weechat_string_dyn_concat (out, "\\n", -1);
+ ptr_string++;
+ break;
+ default:
+ length = weechat_utf8_char_size ((char *)ptr_string);
+ if (length == 0)
+ length = 1;
+ weechat_string_dyn_concat (out,
+ (const char *)ptr_string,
+ length);
+ ptr_string += length;
+ break;
+ }
+ }
+
+ result = *out;
+ weechat_string_dyn_free (out, 0);
+
+ return result;
+}
+
+/*
+ * Unescapes a tag value.
+ *
+ * See: https://ircv3.net/specs/extensions/message-tags#escaping-values
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+irc_tag_unescape_value (const char *string)
+{
+ char **out, *result;
+ unsigned char *ptr_string;
+ int length;
+
+ if (!string)
+ return NULL;
+
+ length = strlen (string);
+ out = weechat_string_dyn_alloc (length + (length / 2) + 1);
+ if (!out)
+ return NULL;
+
+ ptr_string = (unsigned char *)string;
+ while (ptr_string && ptr_string[0])
+ {
+ switch (ptr_string[0])
+ {
+ case '\\':
+ ptr_string++;
+ switch (ptr_string[0])
+ {
+ case ':':
+ weechat_string_dyn_concat (out, ";", -1);
+ ptr_string++;
+ break;
+ case 's':
+ weechat_string_dyn_concat (out, " ", -1);
+ ptr_string++;
+ break;
+ case '\\':
+ weechat_string_dyn_concat (out, "\\", -1);
+ ptr_string++;
+ break;
+ case 'r':
+ weechat_string_dyn_concat (out, "\r", -1);
+ ptr_string++;
+ break;
+ case 'n':
+ weechat_string_dyn_concat (out, "\n", -1);
+ ptr_string++;
+ break;
+ default:
+ if (ptr_string[0])
+ {
+ length = weechat_utf8_char_size ((char *)ptr_string);
+ if (length == 0)
+ length = 1;
+ weechat_string_dyn_concat (out,
+ (const char *)ptr_string,
+ length);
+ ptr_string += length;
+ }
+ break;
+ }
+ break;
+ default:
+ length = weechat_utf8_char_size ((char *)ptr_string);
+ if (length == 0)
+ length = 1;
+ weechat_string_dyn_concat (out,
+ (const char *)ptr_string,
+ length);
+ ptr_string += length;
+ break;
+ }
+ }
+
+ result = *out;
+ weechat_string_dyn_free (out, 0);
+
+ return result;
+}
+
+/*
+ * Callback for modifiers "irc_tag_escape_value" and "irc_tag_unescape_value".
+ *
+ * These modifiers can be used by other plugins to escape/unescape IRC message
+ * tags.
+ */
+
+char *
+irc_tag_modifier_cb (const void *pointer, void *data,
+ const char *modifier, const char *modifier_data,
+ const char *string)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) modifier_data;
+
+ if (strcmp (modifier, "irc_tag_escape_value") == 0)
+ return irc_tag_escape_value (string);
+
+ if (strcmp (modifier, "irc_tag_unescape_value") == 0)
+ return irc_tag_unescape_value (string);
+
+ /* unknown modifier */
+ return NULL;
+}
+
+/*
+ * Parses tags received in an IRC message.
+ * Returns a hashtable with tags and their unescaped values.
+ *
+ * Example:
+ * if tags == "aaa=bbb;ccc;example.com/ddd=eee",
+ * hashtable will have following keys/values:
+ * "aaa" => "bbb"
+ * "ccc" => NULL
+ * "example.com/ddd" => "eee"
+ */
+
+struct t_hashtable *
+irc_tag_parse (const char *tags)
+{
+ struct t_hashtable *hashtable;
+ char **items, *pos, *key, *unescaped;
+ int num_items, i;
+
+ if (!tags || !tags[0])
+ return NULL;
+
+ hashtable = weechat_hashtable_new (32,
+ WEECHAT_HASHTABLE_STRING,
+ WEECHAT_HASHTABLE_STRING,
+ NULL, NULL);
+ if (!hashtable)
+ return NULL;
+
+ items = weechat_string_split (tags, ";", NULL,
+ WEECHAT_STRING_SPLIT_STRIP_LEFT
+ | WEECHAT_STRING_SPLIT_STRIP_RIGHT
+ | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
+ 0, &num_items);
+ if (items)
+ {
+ for (i = 0; i < num_items; i++)
+ {
+ pos = strchr (items[i], '=');
+ if (pos)
+ {
+ /* format: "tag=value" */
+ key = weechat_strndup (items[i], pos - items[i]);
+ if (key)
+ {
+ unescaped = irc_tag_unescape_value (pos + 1);
+ weechat_hashtable_set (hashtable, key, unescaped);
+ if (unescaped)
+ free (unescaped);
+ free (key);
+ }
+ }
+ else
+ {
+ /* format: "tag" */
+ weechat_hashtable_set (hashtable, items[i], NULL);
+ }
+ }
+ weechat_string_free_split (items);
+ }
+
+ return hashtable;
+}