Revision control

Copy as Markdown

Other Tools

/* 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
* RFC Drafts: IRC Client Capabilities
* IRCv3
*
* 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.
const 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.
const 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("=")) {
const 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.
const 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;
},
},
};