From db8af19dae9b6e6f40fa470c325ad0257896e471 Mon Sep 17 00:00:00 2001 From: Darren Date: Sat, 5 Jan 2013 02:39:51 +0000 Subject: [PATCH] server/irc/connection.js tidy + comments --- server/irc/connection.js | 208 ++++++++++++++++++++++++++++----------- 1 file changed, 149 insertions(+), 59 deletions(-) diff --git a/server/irc/connection.js b/server/irc/connection.js index 101872b..01d4743 100644 --- a/server/irc/connection.js +++ b/server/irc/connection.js @@ -4,91 +4,159 @@ var net = require('net'), util = require('util'), _ = require('lodash'); + var IrcConnection = function (hostname, port, ssl, nick, user, pass) { var that = this; events.EventEmitter.call(this); + // Socket state this.connected = false; + + // If registeration with the IRCd has completed this.registered = false; + + // If we are in the CAP negotiation stage this.cap_negotiation = true; + + // User information this.nick = nick; this.user = user; // Contains users real hostname and address - this.username = this.nick.replace(/[^0-9a-zA-Z\-_.]/, ''), + this.username = this.nick.replace(/[^0-9a-zA-Z\-_.]/, ''); + this.password = pass; + + // IRC connection information this.irc_host = {hostname: hostname, port: port}; this.ssl = !(!ssl); + + // Options sent by the IRCd this.options = Object.create(null); this.cap = {requested: [], enabled: []}; + + // Is SASL supported on the IRCd this.sasl = false; - this.password = pass; + // Buffers for data sent from the IRCd this.hold_last = false; this.held_data = ''; - global.modules.emit('irc:connecting', {connection: this}).done(function () { - that.connect(); - }); + + // Call any modules before making the connection + global.modules.emit('irc:connecting', {connection: this}) + .done(function () { + that.connect(); + }); }; util.inherits(IrcConnection, events.EventEmitter); module.exports.IrcConnection = IrcConnection; + + +/** + * Start the connection to the IRCd + */ IrcConnection.prototype.connect = function () { var that = this; + // The socket connect event to listener for + var socket_connect_event_name = 'connect'; + + + // Make sure we don't already have an open connection + this.disposeSocket(); + + // Open either a secure or plain text socket if (this.ssl) { this.socket = tls.connect({ host: this.irc_host.hostname, port: this.irc_host.port, rejectUnauthorized: global.config.reject_unauthorised_certificates - }, function () { - connect_handler.apply(that, arguments); }); + + socket_connect_event_name = 'secureConnect'; + } else { - this.socket = net.createConnection(this.irc_host.port, this.irc_host.hostname); - this.socket.on('connect', function () { - connect_handler.apply(that, arguments); + this.socket = net.connect({ + host: this.irc_host.hostname, + port: this.irc_host.port }); } - + + this.socket.setEncoding('utf-8'); + + this.socket.on(socket_connect_event_name, function () { + that.connected = true; + socketConnectHandler.apply(that, arguments); + }); + this.socket.on('error', function (event) { that.emit('error', event); + }); - this.socket.setEncoding('utf-8'); - this.socket.on('data', function () { parse.apply(that, arguments); }); - this.socket.on('close', function () { + this.socket.on('close', function (had_error) { + that.connected = false; that.emit('close'); + + // Close the whole socket down + that.disposeSocket(); }); }; + + +/** + * Write a line of data to the IRCd + */ IrcConnection.prototype.write = function (data, callback) { - write.call(this, data + '\r\n', 'utf-8', callback); + this.socket.write(data + '\r\n', 'utf-8', callback); }; + + +/** + * Close the connection to the IRCd after sending one last line + */ IrcConnection.prototype.end = function (data, callback) { - end.call(this, data + '\r\n', 'utf-8', callback); + if (data) + this.write(data); + + this.socket.end(); }; + + +/** + * Clean up this IrcConnection instance and any sockets + */ IrcConnection.prototype.dispose = function () { + this.disposeSocket(); this.removeAllListeners(); }; -var write = function (data, encoding, callback) { - this.socket.write(data, encoding, callback); -}; -var end = function (data, encoding, callback) { - this.socket.end(data, encoding, callback); +/** + * Clean up any sockets for this IrcConnection + */ +IrcConnection.prototype.disposeSocket = function () { + if (this.socket) { + this.socket.removeAllListeners(); + this.socket = null; + } }; -var connect_handler = function () { + +/** + * Handle the socket connect event, starting the IRCd registration + */ +var socketConnectHandler = function () { var that = this, connect_data; @@ -113,27 +181,34 @@ var connect_handler = function () { that.write('CAP LS'); - if (that.password) { + if (that.password) that.write('PASS ' + that.password); - } + that.write('NICK ' + that.nick); that.write('USER ' + that.username + ' 0 0 :' + '[www.kiwiirc.com] ' + that.nick); - that.connected = true; that.emit('connected'); }); }; + +/** + * Load any WEBIRC or alternative settings for this connection + * Called in scope of the IrcConnection instance + */ function findWebIrc(connect_data) { - var webirc_pass = global.config.webirc_pass; - var ip_as_username = global.config.ip_as_username; - var tmp; + var webirc_pass = global.config.webirc_pass, + ip_as_username = global.config.ip_as_username, + tmp; + // Do we have a WEBIRC password for this? if (webirc_pass && webirc_pass[this.irc_host.hostname]) { + // Build the WEBIRC line to be sent before IRC registration tmp = 'WEBIRC ' + webirc_pass[this.irc_host.hostname] + ' KiwiIRC '; tmp += this.user.hostname + ' ' + this.user.address; + connect_data.prepend_data = [tmp]; } @@ -152,7 +227,13 @@ function findWebIrc(connect_data) { -parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; +/** + * The regex that parses a line of data from the IRCd + * Deviates from the RFC a little to support the '/' character now used in some + * IRCds + */ +var parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; + var parse = function (data) { var i, msg, @@ -162,48 +243,57 @@ var parse = function (data) { tags = [], tag; - if ((this.hold_last) && (this.held_data !== '')) { + if (this.hold_last && this.held_data !== '') { data = this.held_data + data; this.hold_last = false; this.held_data = ''; } + + // If the last line is incomplete, hold it until we have more data if (data.substr(-1) !== '\n') { this.hold_last = true; } + + // Process our data line by line data = data.split("\n"); for (i = 0; i < data.length; i++) { - if (data[i]) { - if ((this.hold_last) && (i === data.length - 1)) { - this.held_data = data[i]; - break; - } + if (!data[i]) break; + + // If flagged to hold the last line, store it and move on + if (this.hold_last && (i === data.length - 1)) { + this.held_data = data[i]; + break; + } + + // Parse the complete line, removing any carriage returns + msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, '')); - // We have a complete line of data, parse it! - msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, '')); - if (msg) { - if (msg[1]) { - tags = msg[1].split(';'); - for (j = 0; j < tags.length; j++) { - tag = tags[j].split('='); - tags[j] = {tag: tag[0], value: tag[1]}; - } + if (msg) { + if (msg[1]) { + tags = msg[1].split(';'); + for (j = 0; j < tags.length; j++) { + tag = tags[j].split('='); + tags[j] = {tag: tag[0], value: tag[1]}; } - msg = { - tags: tags, - prefix: msg[2], - nick: msg[3], - ident: msg[4], - hostname: msg[5] || '', - command: msg[6], - params: msg[7] || '', - trailing: (msg[8]) ? msg[8].trim() : '' - }; - msg.params = msg.params.split(' '); - - this.emit('irc_' + msg.command.toUpperCase(), msg); - } else { - console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); } + msg = { + tags: tags, + prefix: msg[2], + nick: msg[3], + ident: msg[4], + hostname: msg[5] || '', + command: msg[6], + params: msg[7] || '', + trailing: (msg[8]) ? msg[8].trim() : '' + }; + msg.params = msg.params.split(' '); + + this.emit('irc_' + msg.command.toUpperCase(), msg); + + } else { + + // The line was not parsed correctly, must be malformed + console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); } } }; -- 2.25.1