summaryrefslogtreecommitdiff
path: root/libpurple/protocols
diff options
context:
space:
mode:
authorGary Kramlich <grim@reaperworld.com>2023-02-16 08:13:28 -0600
committerGary Kramlich <grim@reaperworld.com>2023-02-16 08:13:28 -0600
commit8cc4bba0eaaae7c6aebca5a40abeb85370bc6073 (patch)
tree9cbb9b1db323f01980f4060ffe37e47d140a3ad5 /libpurple/protocols
parent74543a21099a29466d3f356beebbb2e3973159dd (diff)
downloadpidgin-8cc4bba0eaaae7c6aebca5a40abeb85370bc6073.tar.gz
IRCv3: Negotiate the message-tags capability and make sure our regex matches the BNF
Previously we were missing the client only prefix and we were allowing .'s and -'s in the vendor field. I've updated the regex to support this and split the key into all of it's parts; the key, client only prefix, vendor, and key_name. Right now we only use the full key, but we may need to change the way we represent tags in memory to accomidate all of these fields. This also means we are telling the server we support the TAGMSG command which we do not yet implement, but that should be fine for now. Testing Done: Ran the unit tests Connected to a local ergo instance and verified the capability was ack'd. Connected to libera and discovered they don't currently support message tags. Bugs closed: PIDGIN-17738 Reviewed at https://reviews.imfreedom.org/r/2217/
Diffstat (limited to 'libpurple/protocols')
-rw-r--r--libpurple/protocols/ircv3/README.md3
-rw-r--r--libpurple/protocols/ircv3/purpleircv3capabilities.c31
-rw-r--r--libpurple/protocols/ircv3/purpleircv3capabilities.h20
-rw-r--r--libpurple/protocols/ircv3/purpleircv3parser.c7
-rw-r--r--libpurple/protocols/ircv3/tests/test_ircv3_parser.c91
5 files changed, 144 insertions, 8 deletions
diff --git a/libpurple/protocols/ircv3/README.md b/libpurple/protocols/ircv3/README.md
index b361be288e..3fa2c1fa5a 100644
--- a/libpurple/protocols/ircv3/README.md
+++ b/libpurple/protocols/ircv3/README.md
@@ -16,5 +16,6 @@ This is a list of capabilities that we currently support. We'll do our best to
keep this list up to date, but if you notice we've missed something please let
us know!
-* sasl (right now just PLAIN works)
* cap-notify
+* sasl (right now just PLAIN works)
+* message-tags
diff --git a/libpurple/protocols/ircv3/purpleircv3capabilities.c b/libpurple/protocols/ircv3/purpleircv3capabilities.c
index 2ca7daf2da..35ac8c165c 100644
--- a/libpurple/protocols/ircv3/purpleircv3capabilities.c
+++ b/libpurple/protocols/ircv3/purpleircv3capabilities.c
@@ -108,7 +108,6 @@ purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities
{
PurpleAccount *account = NULL;
PurpleConnection *purple_connection = NULL;
- gboolean found = FALSE;
purple_connection = PURPLE_CONNECTION(capabilities->connection);
account = purple_connection_get_account(purple_connection);
@@ -117,7 +116,10 @@ purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities
* require-password option.
*/
if(purple_account_get_require_password(account)) {
+ gboolean found = FALSE;
+
purple_ircv3_capabilities_lookup(capabilities, "sasl", &found);
+
if(found) {
purple_ircv3_sasl_request(capabilities);
}
@@ -126,10 +128,12 @@ purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities
/* cap-notify is implied when we use CAP LS 302, so this is really just to
* make sure it's requested.
*/
- purple_ircv3_capabilities_lookup(capabilities, "cap-notify", &found);
- if(found) {
- purple_ircv3_capabilities_request(capabilities, "cap-notify");
- }
+ purple_ircv3_capabilities_lookup_and_request(capabilities, "cap-notify");
+
+ /* message-tags is used for a lot of stuff so we need to tell everyone we
+ * do in fact support it.
+ */
+ purple_ircv3_capabilities_lookup_and_request(capabilities, "message-tags");
}
/******************************************************************************
@@ -616,6 +620,23 @@ purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities,
return value;
}
+gboolean
+purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities,
+ const char *name)
+{
+ gboolean found = FALSE;
+
+ g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), FALSE);
+ g_return_val_if_fail(name != NULL, FALSE);
+
+ purple_ircv3_capabilities_lookup(capabilities, name, &found);
+ if(found) {
+ purple_ircv3_capabilities_request(capabilities, name);
+ }
+
+ return found;
+}
+
void
purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities) {
g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
diff --git a/libpurple/protocols/ircv3/purpleircv3capabilities.h b/libpurple/protocols/ircv3/purpleircv3capabilities.h
index 833b87bd03..6041e42e83 100644
--- a/libpurple/protocols/ircv3/purpleircv3capabilities.h
+++ b/libpurple/protocols/ircv3/purpleircv3capabilities.h
@@ -85,6 +85,26 @@ void purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, co
const char *purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, const char *name, gboolean *found);
/**
+ * purple_ircv3_capabilities_lookup_and_request:
+ * @capabilities: The instance.
+ * @name: The name of the capability to look for.
+ *
+ * A helper function to call [method@PurpleIRCv3.Capabilities.Lookup] and if
+ * found, call [method@PurpleIRCv3.Capabilities.Request].
+ *
+ * This method ignores the advertised value, so to get that you'll need to call
+ * [method@PurpleIRCv3.Capabilities.Lookup] yourself.
+ *
+ * Also if you need to do something when the server ACK's or NAK's your
+ * request, you're probably better off just using the methods yourself.
+ *
+ * Returns: %TRUE if @name was found and requested, %FALSE otherwise.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities, const char *name);
+
+/**
* purple_ircv3_capabilties_add_wait:
* @capabilities: The instance.
*
diff --git a/libpurple/protocols/ircv3/purpleircv3parser.c b/libpurple/protocols/ircv3/purpleircv3parser.c
index 8f2fe093ec..4e93cf6cd3 100644
--- a/libpurple/protocols/ircv3/purpleircv3parser.c
+++ b/libpurple/protocols/ircv3/purpleircv3parser.c
@@ -267,8 +267,11 @@ purple_ircv3_parser_init(PurpleIRCv3Parser *parser) {
0, 0, NULL);
g_assert(parser->regex_message != NULL);
- parser->regex_tags = g_regex_new("(?:(?<key>[A-Za-z0-9-\\/]+)"
- "(?:=(?<value>[^\\r\\n;]*))?(?:;|$))",
+ parser->regex_tags = g_regex_new("(?<key>(?<client_prefix>\\+?)"
+ "(?:(?<vendor>[A-Za-z0-9-\\.]+)\\/)?"
+ "(?<key_name>[A-Za-z0-9-]+)"
+ ")"
+ "(?:=(?<value>[^\r\n;]*))?(?:;|$)",
0, 0, NULL);
g_assert(parser->regex_tags != NULL);
diff --git a/libpurple/protocols/ircv3/tests/test_ircv3_parser.c b/libpurple/protocols/ircv3/tests/test_ircv3_parser.c
index 65d67b2217..8fae4ef72f 100644
--- a/libpurple/protocols/ircv3/tests/test_ircv3_parser.c
+++ b/libpurple/protocols/ircv3/tests/test_ircv3_parser.c
@@ -594,6 +594,85 @@ test_purple_ircv3_special_mode_2(void) {
}
/******************************************************************************
+ * Message tags examples
+ *****************************************************************************/
+static void
+test_purple_ircv3_parser_message_tags_none(void) {
+ TestPurpleIRCv3ParserData data = {
+ .source = "nick!ident@host.com",
+ .command = "PRIVMSG",
+ .n_params = 2,
+ .params = {"me", "Hello"},
+ };
+ const char *msg = NULL;
+
+ msg = ":nick!ident@host.com PRIVMSG me :Hello";
+
+ test_purple_ircv3_parser(msg, &data);
+}
+
+static void
+test_purple_ircv3_parser_message_tags_3_tags(void) {
+ TestPurpleIRCv3ParserData data = {
+ .source = "nick!ident@host.com",
+ .command = "PRIVMSG",
+ .n_params = 2,
+ .params = {"me", "Hello"},
+ };
+ const char *msg = NULL;
+
+ data.tags = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(data.tags, "aaa", "bbb");
+ g_hash_table_insert(data.tags, "ccc", "");
+ g_hash_table_insert(data.tags, "example.com/ddd", "eee");
+
+ msg = "@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me "
+ ":Hello";
+
+ test_purple_ircv3_parser(msg, &data);
+}
+
+static void
+test_purple_ircv3_parser_message_tags_client_only(void) {
+ TestPurpleIRCv3ParserData data = {
+ .source = "url_bot!bot@example.com",
+ .command = "PRIVMSG",
+ .n_params = 2,
+ .params = {"#channel", "Example.com: A News Story"},
+ };
+ const char *msg = NULL;
+
+ data.tags = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(data.tags, "+icon", "https://example.com/favicon.png");
+
+ msg = "@+icon=https://example.com/favicon.png :url_bot!bot@example.com "
+ "PRIVMSG #channel :Example.com: A News Story";
+
+ test_purple_ircv3_parser(msg, &data);
+}
+
+static void
+test_purple_ircv3_parser_message_tags_labeled_response(void) {
+ TestPurpleIRCv3ParserData data = {
+ .source = "nick!user@example.com",
+ .command = "TAGMSG",
+ .n_params = 1,
+ .params = {"#channel"},
+ };
+ const char *msg = NULL;
+
+ data.tags = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(data.tags, "label", "123");
+ g_hash_table_insert(data.tags, "msgid", "abc");
+ g_hash_table_insert(data.tags, "+example-client-tag", "example-value");
+
+ msg = "@label=123;msgid=abc;+example-client-tag=example-value "
+ ":nick!user@example.com TAGMSG #channel";
+
+ test_purple_ircv3_parser(msg, &data);
+
+}
+/******************************************************************************
* Main
*****************************************************************************/
gint
@@ -686,5 +765,17 @@ main(gint argc, gchar *argv[]) {
g_test_add_func("/ircv3/parser/special-mode-2",
test_purple_ircv3_special_mode_2);
+ /* These tests are the examples from the message-tags specification,
+ * https://ircv3.net/specs/extensions/message-tags.html.
+ */
+ g_test_add_func("/ircv3/parser/message-tags/none",
+ test_purple_ircv3_parser_message_tags_none);
+ g_test_add_func("/ircv3/parser/message-tags/3-tags",
+ test_purple_ircv3_parser_message_tags_3_tags);
+ g_test_add_func("/ircv3/parser/message-tags/client-only",
+ test_purple_ircv3_parser_message_tags_client_only);
+ g_test_add_func("/ircv3/parser/message-tags/labeled-message",
+ test_purple_ircv3_parser_message_tags_labeled_response);
+
return g_test_run();
}