ircv3: Add support for weird tag names, per the spec
authorJanne Mareike Koschinski <janne@kuschku.de>
Tue, 23 Feb 2021 20:25:03 +0000 (21:25 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 12 Mar 2021 13:22:29 +0000 (14:22 +0100)
IRCv3 currently does not specify (or recommend specifying) any tag
names which would contain multiple slashes, but IRCv3 does recommend
clients should gracefully handle any possible tag, ideally treating
it as opaque string.

We’d like to avoid that, but also want to ensure even after a roundtrip
through our core, tags we don’t support are still in identical
condition.

src/common/ircdecoder.cpp
tests/common/ircdecodertest.cpp
tests/common/ircencodertest.cpp

index cadefbf..5eb0e6d 100644 (file)
@@ -54,9 +54,11 @@ QString IrcDecoder::parseTagValue(const QString& value)
                 result.append(*it);
             }
             escaped = false;
-        } else if (it->unicode() == '\\') {
+        }
+        else if (it->unicode() == '\\') {
             escaped = true;
-        } else {
+        }
+        else {
             result.append(*it);
         }
     }
@@ -125,9 +127,15 @@ QHash<IrcTagKey, QString> IrcDecoder::parseTags(const std::function<QString(cons
         if (key.clientTag) {
             rawKey.remove(0, 1);
         }
-        QList<QString> splitByVendorAndKey = rawKey.split('/');
-        if (!splitByVendorAndKey.isEmpty()) key.key = splitByVendorAndKey.takeLast();
-        if (!splitByVendorAndKey.isEmpty()) key.vendor = splitByVendorAndKey.takeLast();
+
+        int splitIndex = rawKey.lastIndexOf('/');
+        if (splitIndex > 0 && splitIndex + 1 < rawKey.length()) {
+            key.key = rawKey.mid(splitIndex + 1);
+            key.vendor = rawKey.left(splitIndex);
+        }
+        else {
+            key.key = rawKey;
+        }
         tags[key] = parseTagValue(rawValue);
     }
     return tags;
@@ -155,7 +163,12 @@ QByteArray IrcDecoder::parseParameter(const QByteArray& raw, int& start)
     }
 }
 
-void IrcDecoder::parseMessage(const std::function<QString(const QByteArray&)>& decode, const QByteArray& rawMsg, QHash<IrcTagKey, QString>& tags, QString& prefix, QString& command, QList<QByteArray>& parameters)
+void IrcDecoder::parseMessage(const std::function<QString(const QByteArray&)>& decode,
+                              const QByteArray& rawMsg,
+                              QHash<IrcTagKey, QString>& tags,
+                              QString& prefix,
+                              QString& command,
+                              QList<QByteArray>& parameters)
 {
     int start = 0;
     skipEmptyParts(rawMsg, start);
@@ -170,7 +183,6 @@ void IrcDecoder::parseMessage(const std::function<QString(const QByteArray&)>& d
         QByteArray param = parseParameter(rawMsg, start);
         skipEmptyParts(rawMsg, start);
         params.append(param);
-
     }
     parameters = params;
 }
index 0936336..380a038 100644 (file)
@@ -150,6 +150,30 @@ TEST(IrcDecoderTest, with_tags)
                           {IrcTagKey("rt"), "ql7"}},
                          "",
                          "foo"));
+
+    EXPECT_EQ(parse("@a=b foo"),
+              IrcMessage(
+                  {{IrcTagKey("a"),  "b"}},
+                  "",
+                  "foo"));
+
+    EXPECT_EQ(parse("@example.com/a=b foo"),
+              IrcMessage(
+                  {{IrcTagKey("example.com", "a"),  "b"}},
+                  "",
+                  "foo"));
+
+    EXPECT_EQ(parse("@example.com/subfolder/to/a=b foo"),
+              IrcMessage(
+                  {{IrcTagKey("example.com/subfolder/to", "a"),  "b"}},
+                  "",
+                  "foo"));
+
+    EXPECT_EQ(parse("@v\\/e\\/n\\/d\\/o\\/r/tag=b foo"),
+              IrcMessage(
+                  {{IrcTagKey("v\\/e\\/n\\/d\\/o\\/r", "tag"),  "b"}},
+                  "",
+                  "foo"));
 }
 
 TEST(IrcDecoderTest, with_escaped_tags)
index 38b50e0..e723ae0 100644 (file)
@@ -208,6 +208,34 @@ TEST(IrcEncoderTest, tags_with_no_value_and_space_filled_trailing)
                          {"bar", "baz", "  "})).data());
 }
 
+TEST(IrcEncoderTest, tags_with_invalid_vendor)
+{
+    EXPECT_STRCASEEQ(
+        "@a=b foo",
+        write(IrcMessage(
+            {{IrcTagKey("a"),  "b"}},
+            "",
+            "foo")).data());
+    EXPECT_STRCASEEQ(
+        "@example.com/a=b foo",
+        write(IrcMessage(
+            {{IrcTagKey("example.com", "a"),  "b"}},
+            "",
+            "foo")).data());
+    EXPECT_STRCASEEQ(
+        "@example.com/subfolder/to/a=b foo",
+        write(IrcMessage(
+            {{IrcTagKey("example.com/subfolder/to", "a"),  "b"}},
+            "",
+            "foo")).data());
+    EXPECT_STRCASEEQ(
+        "@v\\/e\\/n\\/d\\/o\\/r/tag=b foo",
+        write(IrcMessage(
+            {{IrcTagKey("v\\/e\\/n\\/d\\/o\\/r", "tag"),  "b"}},
+            "",
+            "foo")).data());
+}
+
 TEST(IrcEncoderTest, tags_with_escaped_values)
 {
     std::vector<std::string> expected{