summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircISUPPORT.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/irc/ircISUPPORT.sys.mjs')
-rw-r--r--comm/chat/protocols/irc/ircISUPPORT.sys.mjs246
1 files changed, 246 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/ircISUPPORT.sys.mjs b/comm/chat/protocols/irc/ircISUPPORT.sys.mjs
new file mode 100644
index 0000000000..9d2ce29afb
--- /dev/null
+++ b/comm/chat/protocols/irc/ircISUPPORT.sys.mjs
@@ -0,0 +1,246 @@
+/* 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 ISUPPORT parameters for the 005 numeric to allow a server
+ * to notify a client of what capabilities it supports.
+ * The 005 numeric
+ * http://www.irc.org/tech_docs/005.html
+ * RFC Drafts: IRC RPL_ISUPPORT Numeric Definition
+ * https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03
+ * https://tools.ietf.org/html/draft-hardy-irc-isupport-00
+ */
+
+import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs";
+
+/*
+ * Parses an ircMessage into an ISUPPORT message for each token of the form:
+ * <parameter>=<value> or -<value>
+ * The isupport field is added to the message and it has the following fields:
+ * parameter What is being configured by this ISUPPORT token.
+ * useDefault Whether this parameter should be reset to the default value, as
+ * defined by the RFC.
+ * value The new value for the parameter.
+ */
+function isupportMessage(aMessage) {
+ // Separate the ISUPPORT parameters.
+ let tokens = aMessage.params.slice(1, -1);
+
+ let message = aMessage;
+ message.isupport = {};
+
+ return tokens.map(function (aToken) {
+ let newMessage = JSON.parse(JSON.stringify(message));
+ newMessage.isupport.useDefault = aToken[0] == "-";
+ let token = (
+ newMessage.isupport.useDefault ? aToken.slice(1) : aToken
+ ).split("=");
+ newMessage.isupport.parameter = token[0];
+ newMessage.isupport.value = token[1] || null;
+ return newMessage;
+ });
+}
+
+export var ircISUPPORT = {
+ name: "ISUPPORT",
+ // Slightly above default RFC 2812 priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ isEnabled: () => true,
+
+ commands: {
+ // RPL_ISUPPORT
+ // [-]<parameter>[=<value>] :are supported by this server
+ "005": function (message, ircHandlers) {
+ let messages = isupportMessage(message);
+
+ messages = messages.filter(
+ aMessage => !ircHandlers.handleISUPPORTMessage(this, aMessage)
+ );
+ if (messages.length) {
+ // Display the list of unhandled ISUPPORT messages.
+ let unhandledMessages = messages
+ .map(aMsg => aMsg.isupport.parameter)
+ .join(" ");
+ this.LOG(
+ "Unhandled ISUPPORT messages: " +
+ unhandledMessages +
+ "\nRaw message: " +
+ message.rawMessage
+ );
+ }
+
+ return true;
+ },
+ },
+};
+
+function setSimpleNumber(aAccount, aField, aMessage, aDefaultValue) {
+ let value = aMessage.isupport.value ? Number(aMessage.isupport.value) : null;
+ aAccount[aField] = value && !isNaN(value) ? value : aDefaultValue;
+ return true;
+}
+
+// Generates an expression to search for the ASCII range of a-b.
+function generateNormalize(a, b) {
+ return new RegExp(
+ "[\\x" + a.toString(16) + "-\\x" + b.toString(16) + "]",
+ "g"
+ );
+}
+
+export var isupportBase = {
+ name: "ISUPPORT",
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY,
+ isEnabled: () => true,
+
+ commands: {
+ CASEMAPPING(aMessage) {
+ // CASEMAPPING=<mapping>
+ // Allows the server to specify which method it uses to compare equality
+ // of case-insensitive strings.
+
+ // By default, use rfc1459 type case mapping.
+ let value = aMessage.isupport.useDefault
+ ? "rfc1493"
+ : aMessage.isupport.value;
+
+ // Set the normalize function of the account to use the proper case
+ // mapping.
+ if (value == "ascii") {
+ // The ASCII characters 97 to 122 (decimal) are the lower-case
+ // characters of ASCII 65 to 90 (decimal).
+ this.normalizeExpression = generateNormalize(65, 90);
+ } else if (value == "rfc1493") {
+ // The ASCII characters 97 to 126 (decimal) are the lower-case
+ // characters of ASCII 65 to 94 (decimal).
+ this.normalizeExpression = generateNormalize(65, 94);
+ } else if (value == "strict-rfc1459") {
+ // The ASCII characters 97 to 125 (decimal) are the lower-case
+ // characters of ASCII 65 to 93 (decimal).
+ this.normalizeExpression = generateNormalize(65, 93);
+ }
+ return true;
+ },
+ CHANLIMIT(aMessage) {
+ // CHANLIMIT=<prefix>:<number>[,<prefix>:<number>]*
+ // Note that each <prefix> can actually contain multiple prefixes, this
+ // means the sum of those prefixes is given.
+ this.maxChannels = {};
+
+ let pairs = aMessage.isupport.value.split(",");
+ for (let pair of pairs) {
+ let [prefix, num] = pair.split(":");
+ this.maxChannels[prefix] = num;
+ }
+ return true;
+ },
+ CHANMODES: aMessage => false,
+ CHANNELLEN(aMessage) {
+ // CHANNELLEN=<number>
+ // Default is from RFC 1493.
+ return setSimpleNumber(this, "maxChannelLength", aMessage, 200);
+ },
+ CHANTYPES(aMessage) {
+ // CHANTYPES=[<channel prefix>]*
+ let value = aMessage.isupport.useDefault ? "#&" : aMessage.isupport.value;
+ this.channelPrefixes = value.split("");
+ return true;
+ },
+ EXCEPTS: aMessage => false,
+ IDCHAN: aMessage => false,
+ INVEX: aMessage => false,
+ KICKLEN(aMessage) {
+ // KICKLEN=<number>
+ // Default value is Infinity.
+ return setSimpleNumber(this, "maxKickLength", aMessage, Infinity);
+ },
+ MAXLIST: aMessage => false,
+ MODES: aMessage => false,
+ NETWORK: aMessage => false,
+ NICKLEN(aMessage) {
+ // NICKLEN=<number>
+ // Default value is from RFC 1493.
+ return setSimpleNumber(this, "maxNicknameLength", aMessage, 9);
+ },
+ PREFIX(aMessage) {
+ // PREFIX=[(<mode character>*)<prefix>*]
+ let value = aMessage.isupport.useDefault
+ ? "(ov)@+"
+ : aMessage.isupport.value;
+
+ this.userPrefixToModeMap = {};
+ // A null value specifier indicates that no prefixes are supported.
+ if (!value.length) {
+ return true;
+ }
+
+ let matches = /\(([a-z]*)\)(.*)/i.exec(value);
+ if (!matches) {
+ // The pattern doesn't match.
+ this.WARN("Invalid PREFIX value: " + value);
+ return false;
+ }
+ if (matches[1].length != matches[2].length) {
+ this.WARN(
+ "Invalid PREFIX value, does not provide one-to-one mapping:" + value
+ );
+ return false;
+ }
+
+ for (let i = 0; i < matches[2].length; i++) {
+ this.userPrefixToModeMap[matches[2][i]] = matches[1][i];
+ }
+ return true;
+ },
+ // SAFELIST allows the client to request the server buffer LIST responses to
+ // avoid flooding the client. This is not an issue for us, so just ignore
+ // it.
+ SAFELIST: aMessage => true,
+ // SECURELIST tells us that the server won't send LIST data directly after
+ // connection. Unfortunately, the exact time the client has to wait is
+ // configurable, so we can't do anything with this information.
+ SECURELIST: aMessage => true,
+ STATUSMSG: aMessage => false,
+ STD(aMessage) {
+ // This was never updated as the RFC was never formalized.
+ if (aMessage.isupport.value != "rfcnnnn") {
+ this.WARN("Unknown ISUPPORT numeric form: " + aMessage.isupport.value);
+ }
+ return true;
+ },
+ TARGMAX(aMessage) {
+ // TARGMAX=<command>:<max targets>[,<command>:<max targets>]*
+ if (aMessage.isupport.useDefault) {
+ this.maxTargets = 1;
+ return true;
+ }
+
+ this.maxTargets = {};
+ let commands = aMessage.isupport.value.split(",");
+ for (let i = 0; i < commands.length; i++) {
+ let [command, limitStr] = commands[i].split("=");
+ let limit = limitStr ? Number(limit) : Infinity;
+ if (isNaN(limit)) {
+ this.WARN("Invalid maximum number of targets: " + limitStr);
+ continue;
+ }
+ this.maxTargets[command] = limit;
+ }
+ return true;
+ },
+ TOPICLEN(aMessage) {
+ // TOPICLEN=<number>
+ // Default value is Infinity.
+ return setSimpleNumber(this, "maxTopicLength", aMessage, Infinity);
+ },
+
+ // The following are considered "obsolete" by the RFC, but are still in use.
+ CHARSET: aMessage => false,
+ MAXBANS: aMessage => false,
+ MAXCHANNELS: aMessage => false,
+ MAXTARGETS(aMessage) {
+ return setSimpleNumber(this, "maxTargets", aMessage, 1);
+ },
+ },
+};