Bug 1309063 - IRC: treat CAP capabilities as opaque strings. r=freaktechnik a=rkent
authorPatrick Cloke <clokep@gmail.com>
Wed, 19 Oct 2016 13:37:20 -0400
changeset 24870 5120ef957c0ab1800ecc78b40cfa715d08c40de7
parent 24869 af893d6214a03e05974fc2aa47a0a54863ff9b53
child 24871 bca32ff1396e7ba52b179e75b9e9d17fc787269e
push id149
push userkent@caspia.com
push dateMon, 23 Jan 2017 22:07:13 +0000
treeherdercomm-esr45@bca32ff1396e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfreaktechnik, rkent
bugs1309063
Bug 1309063 - IRC: treat CAP capabilities as opaque strings. r=freaktechnik a=rkent
chat/protocols/irc/ircCAP.jsm
chat/protocols/irc/test/test_ircCAP.js
chat/protocols/irc/test/xpcshell.ini
--- a/chat/protocols/irc/ircCAP.jsm
+++ b/chat/protocols/irc/ircCAP.jsm
@@ -4,57 +4,65 @@
 
 /*
  * This implements the IRC Client Capabilities sub-protocol.
  *   Client Capab Proposal
  *     http://www.leeh.co.uk/ircd/client-cap.txt
  *   RFC Drafts: IRC Client Capabilities
  *     http://tools.ietf.org/html/draft-baudis-irc-capab-00
  *     http://tools.ietf.org/html/draft-mitchell-irc-capabilities-01
+ *   IRCv3
+ *     http://ircv3.net/specs/core/capability-negotiation-3.1.html
+ *     http://ircv3.net/specs/core/capability-negotiation-3.2.html
  *
  * Note that this doesn't include any implementation as these RFCs do not even
  * include example parameters.
  */
 
 this.EXPORTED_SYMBOLS = ["ircCAP"];
 
 var Cu = Components.utils;
 
 Cu.import("resource:///modules/ircHandlers.jsm");
 Cu.import("resource:///modules/ircUtils.jsm");
 
-// This matches a modifier, followed by the name spaces (network specific or
-// standardized), followed by the capability name.
-var capParameterExp = /^([\-=~])?((?:(?:[A-Z][A-Z0-9\-]*\.)*[A-Z][A-Z0-9\-]*\/)?[A-Z][A-Z0-9\-]*)$/i;
-
 /*
  * Parses a CAP message of the form:
  *   CAP <subcommand> [<parameters>]
  * The cap field is added to the message and it has the following fields:
  *   subcommand
  *   parameters A list of capabilities.
  */
 function capMessage(aMessage) {
   // The CAP parameters are space separated as the last parameter.
   let parameters = aMessage.params.slice(-1)[0].trim().split(" ");
+  // The subcommand is the second parameter...although sometimes it's the first
+  // parameter.
   aMessage.cap = {
     subcommand: aMessage.params[aMessage.params.length == 3 ? 1 : 0]
   };
 
   return parameters.map(function(aParameter) {
     // Clone the original object.
-    let message = JSON.parse(JSON.stringify(aMessage));
-    let matches = aParameter.match(capParameterExp);
+    let message = Object.assign({}, aMessage);
+    message.cap = Object.assign({}, aMessage.cap);
 
-    message.cap.modifier = matches[1];
+    // If there's a modifier...pull it off. (This is pretty much unused, but we
+    // have to pull it off for backward compatibility.)
+    if ('-=~'.includes(aParameter[0])) {
+      message.cap.modifier = aParameter[0];
+      aParameter = aParameter.substr(1);
+    } else
+      message.cap.modifier = undefined;
+
     // The names are case insensitive, arbitrarily choose lowercase.
-    message.cap.parameter = matches[2].toLowerCase();
-    message.cap.disable = matches[1] == "-";
-    message.cap.sticky = matches[1] == "=";
-    message.cap.ack = matches[1] == "~";
+    message.cap.parameter = aParameter.toLowerCase();
+    message.cap.disable = message.cap.modifier == "-";
+    message.cap.sticky = message.cap.modifier == "=";
+    message.cap.ack = message.cap.modifier == "~";
 
     return message;
   });
 }
 
 var ircCAP = {
   name: "Client Capabilities",
   // Slightly above default RFC 2812 priority.
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/test/test_ircCAP.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+var cap = {};
+Services.scriptloader.loadSubScript("resource:///modules/ircCAP.jsm", cap);
+
+var testData = [
+  // A normal LS from the server.
+  [
+    ["*", "LS", "multi-prefix sasl userhost-in-names"],
+    [{
+      subcommand: 'LS',
+      parameter: 'multi-prefix',
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'sasl',
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'userhost-in-names',
+    }]
+  ],
+
+  // LS with both valid and invalid vendor specific capabilities.
+  [
+    ["*","LS","sasl server-time znc.in/server-time-iso znc.in/playback palaverapp.com"],
+    [{
+      subcommand: 'LS',
+      parameter: 'sasl',
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'server-time',
+    },
+    // Valid vendor prefixes (of the form <domain name>/<capability>).
+    {
+      subcommand: 'LS',
+      parameter: 'znc.in/server-time-iso',
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'znc.in/playback',
+    },
+    // Invalid vendor prefix, but we should treat it as an opaque identifier.
+    {
+      subcommand: 'LS',
+      parameter: 'palaverapp.com',
+    }]
+  ],
+
+  // Some implementations include one less parameter.
+  [
+    ["LS", "sasl"],
+    [{
+      subcommand: 'LS',
+      parameter: 'sasl',
+    }],
+  ],
+
+  // Modifier tests, ensure the modified is stripped from the capaibility and is
+  // parsed correctly.
+  [
+    ["LS", "-disable =sticky ~ack"],
+    [{
+      subcommand: 'LS',
+      parameter: 'disable',
+      modifier: '-',
+      disable: true,
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'sticky',
+      modifier: '=',
+      sticky: true,
+    },
+    {
+      subcommand: 'LS',
+      parameter: 'ack',
+      modifier: '~',
+      ack: true,
+    }],
+  ]
+];
+
+function run_test() {
+  add_test(testCapMessages);
+
+  run_next_test();
+}
+
+/*
+ * Test round tripping parsing and then rebuilding the messages from RFC 2812.
+ */
+function testCapMessages() {
+  for (let data of testData) {
+    // Generate an ircMessage to send into capMessage.
+    let message = {
+      params: data[0]
+    };
+
+    // Create the CAP message.
+    let outputs = cap.capMessage(message);
+
+    // The original message should get a cap object added with the subcommand
+    // set.
+    ok(message.cap);
+    equal(message.cap.subcommand, data[1][0].subcommand);
+
+    // We only care about the "cap" part of each return message.
+    outputs = outputs.map((o) => o.cap);
+
+    // Ensure the expected output is an array.
+    let expectedCaps = data[1];
+    if (!Array.isArray(expectedCaps))
+      expectedCaps = [expectedCaps];
+
+    // Add defaults to the expected output.
+    for (let expectedCap of expectedCaps) {
+      // By default there's no modifier.
+      if (!('modifier' in expectedCap))
+        expectedCap.modifier = undefined;
+      for (let param of ['disable', 'sticky', 'ack']) {
+        if (!(param in expectedCap))
+          expectedCap[param] = false;
+      }
+    }
+
+    // Ensure each item in the arrays are equal.
+    deepEqual(outputs, expectedCaps);
+  }
+
+  run_next_test();
+}
--- a/chat/protocols/irc/test/xpcshell.ini
+++ b/chat/protocols/irc/test/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 head =
 tail =
 
 [test_ctcpFormatting.js]
 [test_ctcpColoring.js]
 [test_ctcpDequote.js]
 [test_ctcpQuote.js]
+[test_ircCAP.js]
 [test_ircMessage.js]
 [test_ircNonStandard.js]
 [test_sendBufferedCommand.js]
 [test_setMode.js]
 [test_splitLongMessages.js]
 [test_tryNewNick.js]