From 772a4bb6ffbc812f2f1b9d12e52db070b9f68afb Mon Sep 17 00:00:00 2001 From: Darren Date: Sun, 21 Oct 2012 15:02:21 +0100 Subject: [PATCH] client + ircconnection cleanup --- server/client.js | 49 +++++++-------- server/ircconnection.js | 136 ++++++++++++++++++++++++++++++++++++++++ server/weblistener.js | 8 +-- 3 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 server/ircconnection.js diff --git a/server/client.js b/server/client.js index 4b31da7..46515e9 100755 --- a/server/client.js +++ b/server/client.js @@ -1,17 +1,18 @@ var util = require('util'), events = require('events'), _ = require('underscore'), - IRCConnection = require('./irc-connection.js').IRCConnection, - IRCCommands = require('./irc-commands.js'), + IrcConnection = require('./ircconnection.js').IrcConnection, + IrcCommands = require('./irc-commands.js'), ClientCommandset = require('./client-commands.js').ClientCommandset; + var Client = function (websocket) { - var c = this; + var that = this; events.EventEmitter.call(this); this.websocket = websocket; - this.IRC_connections = []; + this.irc_connections = []; this.next_connection = 0; this.buffer = { @@ -23,16 +24,16 @@ var Client = function (websocket) { this.client_commands = new ClientCommandset(this); websocket.on('irc', function () { - handleClientMessage.apply(c, arguments); + handleClientMessage.apply(that, arguments); }); websocket.on('kiwi', function () { - kiwi_command.apply(c, arguments); + kiwiCommand.apply(that, arguments); }); websocket.on('disconnect', function () { - disconnect.apply(c, arguments); + websocketDisconnect.apply(that, arguments); }); websocket.on('error', function () { - error.apply(c, arguments); + websocketError.apply(that, arguments); }); }; util.inherits(Client, events.EventEmitter); @@ -55,18 +56,18 @@ Client.prototype.sendKiwiCommand = function (command, callback) { this.websocket.emit('kiwi', command, callback); }; -var handleClientMessage = function (msg, callback) { +function handleClientMessage(msg, callback) { var server, args, obj, channels, keys; // Make sure we have a server number specified if ((msg.server === null) || (typeof msg.server !== 'number')) { return (typeof callback === 'function') ? callback('server not specified') : undefined; - } else if (!this.IRC_connections[msg.server]) { + } else if (!this.irc_connections[msg.server]) { return (typeof callback === 'function') ? callback('not connected to server') : undefined; } // The server this command is directed to - server = this.IRC_connections[msg.server]; + server = this.irc_connections[msg.server]; if (typeof callback !== 'function') { callback = null; @@ -86,23 +87,23 @@ var handleClientMessage = function (msg, callback) { -var kiwi_command = function (command, callback) { +function kiwiCommand(command, callback) { var that = this; - console.log(typeof callback); + if (typeof callback !== 'function') { callback = function () {}; } switch (command.command) { case 'connect': if ((command.hostname) && (command.port) && (command.nick)) { - var con = new IRCConnection(command.hostname, command.port, command.ssl, + var con = new IrcConnection(command.hostname, command.port, command.ssl, command.nick, {hostname: this.websocket.handshake.revdns, address: this.websocket.handshake.address.address}, command.password, null); var con_num = this.next_connection++; - this.IRC_connections[con_num] = con; + this.irc_connections[con_num] = con; - var binder = new IRCCommands.Binder(con, con_num, this); + var binder = new IrcCommands.Binder(con, con_num, this); binder.bind_irc_commands(); con.on('connected', function () { @@ -115,7 +116,7 @@ var kiwi_command = function (command, callback) { }); con.on('close', function () { - that.IRC_connections[con_num] = null; + that.irc_connections[con_num] = null; }); } else { return callback('Hostname, port and nickname must be specified'); @@ -126,14 +127,10 @@ var kiwi_command = function (command, callback) { } }; -var extension_command = function (command, callback) { - if (typeof callback === 'function') { - callback('not implemented'); - } -}; -var disconnect = function () { - _.each(this.IRC_connections, function (irc_connection, i, cons) { +// Websocket has disconnected, so quit all the IRC connections +function websocketDisconnect() { + _.each(this.irc_connections, function (irc_connection, i, cons) { if (irc_connection) { irc_connection.end('QUIT :Kiwi IRC'); cons[i] = null; @@ -142,6 +139,8 @@ var disconnect = function () { this.emit('destroy'); }; -var error = function () { + +// TODO: Should this close all the websocket connections too? +function websocketError() { this.emit('destroy'); }; diff --git a/server/ircconnection.js b/server/ircconnection.js new file mode 100644 index 0000000..68abf1d --- /dev/null +++ b/server/ircconnection.js @@ -0,0 +1,136 @@ +var net = require('net'), + tls = require('tls'), + events = require('events'), + util = require('util'); + +var IrcConnection = function (hostname, port, ssl, nick, user, pass, webirc) { + var that = this; + events.EventEmitter.call(this); + + if (ssl) { + this.socket = tls.connect(port, hostname, {}, connect_handler); + } else { + this.socket = net.createConnection(port, hostname); + this.socket.on('connect', function () { + connect_handler.apply(that, arguments); + }); + } + + this.socket.on('error', function () { + var a = Array.prototype.slice.call(arguments); + a.unshift('error'); + that.emit.apply(this, a); + }); + + this.socket.setEncoding('utf-8'); + + this.socket.on('data', function () { + parse.apply(that, arguments); + }); + + this.socket.on('close', function () { + that.emit('close'); + }); + + this.connected = false; + this.registered = false; + this.nick = nick; + this.user = user; + this.ssl = !(!ssl); + this.options = Object.create(null); + + this.webirc = webirc; + this.password = pass; + this.hold_last = false; + this.held_data = ''; +}; +util.inherits(IrcConnection, events.EventEmitter); + +IrcConnection.prototype.write = function (data, callback) { + console.log('S<--', data); + write.call(this, data + '\r\n', 'utf-8', callback); +}; + +IrcConnection.prototype.end = function (data, callback) { + console.log('S<--', data); + console.log('Closing docket'); + end.call(this, data + '\r\n', 'utf-8', callback); +}; + +var write = function (data, encoding, callback) { + this.socket.write(data, encoding, callback); +}; + +var end = function (data, encoding, callback) { + this.socket.end(data, encoding, callback); +}; + +module.exports.IrcConnection = IrcConnection; + +var connect_handler = function () { + if (this.webirc) { + this.write('WEBIRC ' + this.webirc.pass + ' KiwiIRC ' + this.user.hostname + ' ' + this.user.address); + } + if (this.password) { + this.write('PASS ' + this.password); + } + //this.write('CAP LS'); + this.write('NICK ' + this.nick); + this.write('USER kiwi_' + this.nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + this.nick); + + this.connected = true; + console.log("IrcConnection.emit('connected')"); + this.emit('connected'); +}; + +parse_regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; +//alt_regex = /(?::(([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|[\x5b-\x7d][\x2d0-9\x5b-\x7d]{0,8})(?:(?:!([\x01-\t\v\f\x0e-\x1f!-\x3f\x5b-\xff]+))?@([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}|[0-9a-f]+(?::[0-9a-f]+){7}|0:0:0:0:0:(?:0|ffff):\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}))?)\x20)?([a-z]+|\d{3})((?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){0,14}(?:\x20:[\x01-\t\v\f\x0e-@\x5b-\xff]*)?|(?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){14}(?:\x20:?[\x01-\t\v\f\x0e-@\x5b-\xff]*)?)?/i; + +var parse = function (data) { + var i, + msg, + msg2, + trm; + + if ((this.hold_last) && (this.held_data !== '')) { + data = this.held_data + data; + this.hold_last = false; + this.held_data = ''; + } + if (data.substr(-1) !== '\n') { + this.hold_last = true; + } + 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; + } + + // We have a complete line of data, parse it! + msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, '')); + //msg2 = alt_regex.exec(data[i].replace(/^\r+|\r+$/, '')); + console.log('S-->', data[i]); + console.log('Matches', msg); + if (msg) { + msg = { + prefix: msg[1], + nick: msg[2], + ident: msg[3], + hostname: msg[4] || '', + command: msg[5], + params: msg[6] || '', + trailing: (msg[7]) ? msg[7].trim() : '' + }; + msg.params = msg.params.split(' '); + + console.log('Parsed', msg); + + this.emit('irc_' + msg.command.toUpperCase(), msg); + } else { + console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); + } + } + } +}; diff --git a/server/weblistener.js b/server/weblistener.js index e35a112..60e58ff 100644 --- a/server/weblistener.js +++ b/server/weblistener.js @@ -59,8 +59,8 @@ var WebListener = function (config, transports) { this.ws.enable('browser client etag'); this.ws.set('transports', transports); - this.ws.of('/kiwi').authorization(authorisation).on('connection', function () { - connection.apply(that, arguments); + this.ws.of('/kiwi').authorization(authoriseConnection).on('connection', function () { + newConnection.apply(that, arguments); }); this.ws.of('/kiwi').on('error', console.log); }; @@ -78,14 +78,14 @@ function handleHttpRequest(request, response) { } -var authorisation = function (handshakeData, callback) { +function authoriseConnection(handshakeData, callback) { dns.reverse(handshakeData.address.address, function (err, domains) { handshakeData.revdns = (err) ? handshakeData.address.address : _.first(domains); callback(null, true); }); }; -var connection = function (websocket) { +function newConnection(websocket) { //console.log(websocket); this.emit('connection', new Client(websocket)); }; -- 2.25.1