summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircCAP.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/irc/ircCAP.sys.mjs')
-rw-r--r--comm/chat/protocols/irc/ircCAP.sys.mjs170
1 files changed, 170 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/ircCAP.sys.mjs b/comm/chat/protocols/irc/ircCAP.sys.mjs
new file mode 100644
index 0000000000..3bcff48d8b
--- /dev/null
+++ b/comm/chat/protocols/irc/ircCAP.sys.mjs
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * 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
+ * https://ircv3.net/specs/core/capability-negotiation.html
+ *
+ * Note that this doesn't include any implementation as these RFCs do not even
+ * include example parameters.
+ */
+
+import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs";
+
+/*
+ * Parses a CAP message of the form:
+ * CAP [*|<user>] <subcommand> [*] [<parameters>]
+ * The cap field is added to the message and it has the following fields:
+ * subcommand
+ * parameters A list of capabilities.
+ */
+export function capMessage(aMessage, aAccount) {
+ // 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],
+ };
+
+ const messages = parameters.map(function (aParameter) {
+ // Clone the original object.
+ let message = Object.assign({}, aMessage);
+ message.cap = Object.assign({}, aMessage.cap);
+
+ // 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;
+ }
+
+ // CAP v3.2 capability value
+ if (aParameter.includes("=")) {
+ let paramParts = aParameter.split("=");
+ aParameter = paramParts[0];
+ // The value itself may contain an = sign, join the rest of the parts back together.
+ message.cap.value = paramParts.slice(1).join("=");
+ }
+
+ // The names are case insensitive, arbitrarily choose lowercase.
+ 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;
+ });
+
+ // Queue up messages if the server is indicating multiple lines of caps to list.
+ if (
+ (aMessage.cap.subcommand === "LS" || aMessage.cap.subcommand === "LIST") &&
+ aMessage.params.length == 4
+ ) {
+ aAccount._queuedCAPs = aAccount._queuedCAPs.concat(messages);
+ return [];
+ }
+
+ const retMessages = aAccount._queuedCAPs.concat(messages);
+ aAccount._queuedCAPs.length = 0;
+ return retMessages;
+}
+
+export var ircCAP = {
+ name: "Client Capabilities",
+ // Slightly above default RFC 2812 priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ isEnabled: () => true,
+
+ commands: {
+ CAP(message, ircHandlers) {
+ // [* | <nick>] <subcommand> :<parameters>
+ let messages = capMessage(message, this);
+
+ for (const capCommandMessage of messages) {
+ if (
+ capCommandMessage.cap.subcommand === "LS" ||
+ capCommandMessage.cap.subcommand === "NEW"
+ ) {
+ this._availableCAPs.add(capCommandMessage.cap.parameter);
+ } else if (capCommandMessage.cap.subcommand === "ACK") {
+ this._activeCAPs.add(capCommandMessage.cap.parameter);
+ } else if (capCommandMessage.cap.subcommand === "DEL") {
+ this._availableCAPs.delete(capCommandMessage.cap.parameter);
+ this._activeCAPs.delete(capCommandMessage.cap.parameter);
+ }
+ }
+
+ messages = messages.filter(
+ aMessage => !ircHandlers.handleCAPMessage(this, aMessage)
+ );
+ if (messages.length) {
+ // Display the list of unhandled CAP messages.
+ let unhandledMessages = messages
+ .map(aMsg => aMsg.cap.parameter)
+ .join(" ");
+ this.LOG(
+ "Unhandled CAP messages: " +
+ unhandledMessages +
+ "\nRaw message: " +
+ message.rawMessage
+ );
+ }
+
+ // If no CAP handlers were added, just tell the server we're done.
+ if (
+ message.cap.subcommand == "LS" &&
+ !this._requestedCAPs.size &&
+ !this._queuedCAPs.length
+ ) {
+ this.sendMessage("CAP", "END");
+ this._negotiatedCAPs = true;
+ }
+ return true;
+ },
+
+ 410(aMessage) {
+ // ERR_INVALIDCAPCMD
+ // <unrecognized subcommand> :Invalid CAP subcommand
+ this.WARN("Invalid subcommand: " + aMessage.params[1]);
+ return true;
+ },
+ },
+};
+
+export var capNotify = {
+ name: "Client Capabilities",
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY,
+ // This is implicitly enabled as part of CAP v3.2, so always enable it.
+ isEnabled: () => true,
+
+ commands: {
+ "cap-notify": function (aMessage) {
+ // This negotiation is entirely optional. cap-notify may thus never be formally registered.
+ if (
+ aMessage.cap.subcommand === "LS" ||
+ aMessage.cap.subcommand === "NEW"
+ ) {
+ this.addCAP("cap-notify");
+ this.sendMessage("CAP", ["REQ", "cap-notify"]);
+ } else if (
+ aMessage.cap.subcommand === "ACK" ||
+ aMessage.cap.subcommand === "NAK"
+ ) {
+ this.removeCAP("cap-notify");
+ } else {
+ return false;
+ }
+ return true;
+ },
+ },
+};