<commit_before>var tls = null;
var net = null;
var http = null;
var https = null;
var fs = null;
var url = null;
var dns = null;
var crypto = null;
var ws = null;
var jsp = null;
var pro = null;
var _ = null;
var starttls = null;
var kiwi = null;

this.init = function (objs) {
	tls = objs.tls;
	net = objs.net;
	http = objs.http;
	https = objs.https;
	fs = objs.fs;
	url = objs.url;
	dns = objs.dns;
	crypto = objs.crypto;
	ws = objs.ws;
	jsp = objs.jsp;
	pro = objs.pro;
	_ = objs._;
	starttls = objs.starttls;
	kiwi = require('./kiwi.js');
}






/*
 * Some process changes
 */
this.setTitle = function () {
	process.title = 'kiwiirc';
}

this.changeUser = function () {
    if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
        try {
            process.setgid(kiwi.config.group);
        } catch (err) {
            console.log('Failed to set gid: ' + err);
            process.exit();
        }
    }

    if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
        try {
            process.setuid(kiwi.config.user);
        } catch (e) {
            console.log('Failed to set uid: ' + e);
            process.exit();
        }
    }
};









var ircNumerics = {
    RPL_WELCOME:            '001',
    RPL_MYINFO:             '004',
    RPL_ISUPPORT:           '005',
    RPL_WHOISUSER:          '311',
    RPL_WHOISSERVER:        '312',
    RPL_WHOISOPERATOR:      '313',
    RPL_WHOISIDLE:          '317',
    RPL_ENDOFWHOIS:         '318',
    RPL_WHOISCHANNELS:      '319',
    RPL_NOTOPIC:            '331',
    RPL_TOPIC:              '332',
    RPL_NAMEREPLY:          '353',
    RPL_ENDOFNAMES:         '366',
    RPL_MOTD:               '372',
    RPL_WHOISMODES:         '379',
    ERR_NOSUCHNICK:         '401',
    ERR_CANNOTSENDTOCHAN:   '404',
    ERR_TOOMANYCHANNELS:    '405',
    ERR_NICKNAMEINUSE:      '433',
    ERR_USERNOTINCHANNEL:   '441',
    ERR_NOTONCHANNEL:       '442',
    ERR_NOTREGISTERED:      '451',
    ERR_LINKCHANNEL:        '470',
    ERR_CHANNELISFULL:      '471',
    ERR_INVITEONLYCHAN:     '473',
    ERR_BANNEDFROMCHAN:     '474',
    ERR_BADCHANNELKEY:      '475',
    ERR_CHANOPRIVSNEEDED:   '482',
    RPL_STARTTLS:           '670'
};



this.parseIRCMessage = function (websocket, ircSocket, data) {
    /*global ircSocketDataHandler */
    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj;
    //regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i;
    //regex = /^(?::(\S+) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
    regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;

    msg = regex.exec(data);
    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() : ''
        };
        
        switch (msg.command.toUpperCase()) {
        case 'PING':
            websocket.sendServerLine('PONG ' + msg.trailing);
            break;
        case ircNumerics.RPL_WELCOME:
            if (ircSocket.IRC.CAP.negotiating) {
                ircSocket.IRC.CAP.negotiating = false;
                ircSocket.IRC.CAP.enabled = [];
                ircSocket.IRC.CAP.requested = [];
                ircSocket.IRC.registered = true;
            }
            //regex = /([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)/i;
            //matches = regex.exec(msg.trailing);
            nick =  msg.params.split(' ')[0];
            websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
            break;
        case ircNumerics.RPL_ISUPPORT:
            opts = msg.params.split(" ");
            options = [];
            for (i = 0; i < opts.length; i++) {
                opt = opts[i].split("=", 2);
                opt[0] = opt[0].toUpperCase();
                ircSocket.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
                if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
                    if (opt[0] === 'PREFIX') {
                        regex = /\(([^)]*)\)(.*)/;
                        matches = regex.exec(opt[1]);
                        if ((matches) && (matches.length === 3)) {
                            ircSocket.IRC.options[opt[0]] = [];
                            for (j = 0; j < matches[2].length; j++) {
                                //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
                                ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
                            }

                        }
                    }
                }
            }

            websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
            break;
        case ircNumerics.RPL_WHOISUSER:
        case ircNumerics.RPL_WHOISSERVER:
        case ircNumerics.RPL_WHOISOPERATOR:
        case ircNumerics.RPL_ENDOFWHOIS:
        case ircNumerics.RPL_WHOISCHANNELS:
        case ircNumerics.RPL_WHOISMODES:
            websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
            break;
        case ircNumerics.RPL_WHOISIDLE:
            params = msg.params.split(" ", 4);
            rtn = {server: '', nick: params[1], idle: params[2]};
            if (params[3]) {
                rtn.logon = params[3];
            }
            websocket.sendClientEvent('whois', rtn);
            break;
        case ircNumerics.RPL_MOTD:
            websocket.sendClientEvent('motd', {server: '', "msg": msg.trailing});
            break;
        case ircNumerics.RPL_NAMEREPLY:
            params = msg.params.split(" ");
            nick = params[0];
            chan = params[2];
            users = msg.trailing.split(" ");
            prefixes = _.values(ircSocket.IRC.options.PREFIX);
            nicklist = {};
            i = 0;
            _.each(users, function (user) {
                if (_.include(prefix, user.charAt(0))) {
                    prefix = user.charAt(0);
                    user = user.substring(1);
                    nicklist[user] = prefix;
                } else {
                    nicklist[user] = '';
                }
                if (i++ >= 50) {
                    websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
                    nicklist = {};
                    i = 0;
                }
            });
            if (i > 0) {
                websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
            } else {
                console.log("oops");
            }
            break;
        case ircNumerics.RPL_ENDOFNAMES:
            websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
            break;
        case ircNumerics.ERR_LINKCHANNEL:
            params = msg.params.split(" "); 
            websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
            break;
        case ircNumerics.ERR_NOSUCHNICK:
            websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case 'JOIN':
            // Some BNC's send malformed JOIN causing the channel to be as a
            // parameter instead of trailing.
            if (typeof msg.trailing === 'string' && msg.trailing !== '') {
                channel = msg.trailing;
            } else if (typeof msg.params === 'string' && msg.params !== '') {
                channel = msg.params;
            }

            websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
            if (msg.nick === ircSocket.IRC.nick) {
                websocket.sendServerLine('NAMES ' + msg.trailing);
            }
            break;
        case 'PART':
            websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
            break;
        case 'KICK':
            params = msg.params.split(" ");
            websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
            break;
        case 'QUIT':
            websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
            break;
        case 'NOTICE':
            if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
                // It's a CTCP response
                websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
            } else {
                websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
            }
            break;
        case 'NICK':
            websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
            break;
        case 'TOPIC':
            obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
            websocket.sendClientEvent('topic', obj);
            break;
        case ircNumerics.RPL_TOPIC:
            obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
            websocket.sendClientEvent('topic', obj);
            break;
        case ircNumerics.RPL_NOTOPIC:
            obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
            websocket.sendClientEvent('topic', obj);
            break;
        case 'MODE':
            opts = msg.params.split(" ");
            params = {nick: msg.nick};
            switch (opts.length) {
            case 1:
                params.effected_nick = opts[0];
                params.mode = msg.trailing;
                break;
            case 2:
                params.channel = opts[0];
                params.mode = opts[1];
                break;
            default:
                params.channel = opts[0];
                params.mode = opts[1];
                params.effected_nick = opts[2];
                break;
            }
            websocket.sendClientEvent('mode', params);
            break;
        case 'PRIVMSG':
            if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
                // It's a CTCP request
                if (msg.trailing.substr(1, 6) === 'ACTION') {
                    websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)});
                } else if (msg.trailing.substr(1, 7) === 'VERSION') {
                    ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
                } else {
                    websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
                }
            } else {
                obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
                websocket.sendClientEvent('msg', obj);
            }
            break;
        case 'CAP':
            caps = kiwi.config.cap_options;
            options = msg.trailing.split(" ");
            switch (_.last(msg.params.split(" "))) {
            case 'LS':
                opts = '';
                _.each(_.intersect(caps, options), function (cap) {
                    if (opts !== '') {
                        opts += " ";
                    }
                    opts += cap;
                    ircSocket.IRC.CAP.requested.push(cap);
                });
                if (opts.length > 0) {
                    websocket.sendServerLine('CAP REQ :' + opts);
                } else {
                    websocket.sendServerLine('CAP END');
                }
                // TLS is special
                /*if (_.include(options, 'tls')) {
                    websocket.sendServerLine('STARTTLS');
                    ircSocket.IRC.CAP.requested.push('tls');
                }*/
                break;
            case 'ACK':
                _.each(options, function (cap) {
                    ircSocket.IRC.CAP.enabled.push(cap);
                });
                if (_.last(msg.params.split(" ")) !== '*') {
                    ircSocket.IRC.CAP.requested = [];
                    ircSocket.IRC.CAP.negotiating = false;
                    websocket.sendServerLine('CAP END');
                }
                break;
            case 'NAK':
                ircSocket.IRC.CAP.requested = [];
                ircSocket.IRC.CAP.negotiating = false;
                websocket.sendServerLine('CAP END');
                break;
            }
            break;
        /*case ircNumerics.RPL_STARTTLS:
            try {
                IRC = ircSocket.IRC;
                listeners = ircSocket.listeners('data');
                ircSocket.removeAllListeners('data');
                ssl_socket = starttls(ircSocket, {}, function () {
                    ssl_socket.on("data", function (data) {
                        ircSocketDataHandler(data, websocket, ssl_socket);
                    });
                    ircSocket = ssl_socket;
                    ircSocket.IRC = IRC;
                    _.each(listeners, function (listener) {
                        ircSocket.addListener('data', listener);
                    });
                });
                //console.log(ircSocket);
            } catch (e) {
                console.log(e);
            }
            break;*/
        case ircNumerics.ERR_CANNOTSENDTOCHAN:
            websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_TOOMANYCHANNELS:
            websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_USERNOTINCHANNEL:
            params = msg.params.split(" ");
            websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
            break;
        case ircNumerics.ERR_NOTONCHANNEL:
            websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_CHANNELISFULL:
            websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_INVITEONLYCHAN:
            websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_BANNEDFROMCHAN:
            websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_BADCHANNELKEY:
            websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_CHANOPRIVSNEEDED:
            websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_NICKNAMEINUSE:
            websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
            break;
        case 'ERROR':
            ircSocket.end();
            websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
            websocket.disconnect();
            break;
        case ircNumerics.ERR_NOTREGISTERED:
            if (ircSocket.IRC.registered) {
                console.log('Kiwi thinks user is registered, but the IRC server thinks differently');
            }
            break;
        default:
            console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
        }
    } else {
        console.log("Malformed IRC line: " + data);
    }
};






/*
 * NOTE: Some IRC servers or BNC's out there incorrectly use
 * only \n as a line splitter.
 */
this.ircSocketDataHandler = function (data, websocket, ircSocket) {
    var i;
    if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
        data = ircSocket.held + data;
        ircSocket.holdLast = false;
        ircSocket.held = '';
    }
    if (data.substr(-1) !== '\n') {
        ircSocket.holdLast = true;
    }
    data = data.split("\n");         
    for (i = 0; i < data.length; i++) {
        if (data[i]) {
            if ((ircSocket.holdLast) && (i === data.length - 1)) {
                ircSocket.held = data[i];
                break;
            }

            // We have a complete line of data, parse it!
            kiwi.parseIRCMessage(websocket, ircSocket, data[i].replace(/^\r+|\r+$/, ''));
        }
    }
};





this.httpHandler = function (request, response) {
    var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
        min = {}, public_http_path;
    if (kiwi.config.handle_http) {
        uri = url.parse(request.url, true);
        uri_parts = uri.pathname.split('/');
        
        subs = uri.pathname.substr(0, 4);
        if (uri.pathname === '/js/all.js') {
            if (kiwi.cache.alljs === '') {
                public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;

                min.util = fs.readFileSync(public_http_path + 'js/util.js');
                min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
                min.front = fs.readFileSync(public_http_path + 'js/front.js');
                min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
                min.ast = jsp.parse(min.util + min.gateway + min.front + min.iscroll);
                min.ast = pro.ast_mangle(min.ast);
                min.ast = pro.ast_squeeze(min.ast);
                min.final_code = pro.gen_code(min.ast);
                kiwi.cache.alljs = min.final_code;
                hash = crypto.createHash('md5').update(kiwi.cache.alljs);
                kiwi.cache.alljs_hash = hash.digest('base64');
            }
            if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
                response.statusCode = 304;
            } else {
                response.setHeader('ETag', kiwi.cache.alljs_hash);
                response.write(kiwi.cache.alljs);
            }
            response.end();
        } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
            request.addListener('end', function () {
                kiwi.fileServer.serve(request, response);
            });
        } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
            useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
            if (useragent.match(/android/i) !== -1) {
                agent = 'android';
                touchscreen = true;
            } else if (useragent.match(/iphone/) !== -1) {
                agent = 'iphone';
                touchscreen = true;
            } else if (useragent.match(/ipad/) !== -1) {
                agent = 'ipad';
                touchscreen = true;
            } else if (useragent.match(/ipod/) !== -1) {
                agent = 'ipod';
                touchscreen = true;
            } else {
                agent = 'normal';
                touchscreen = false;
            }
            agent = 'normal';
            touchscreen = false;
            
            if (uri_parts[1] !== 'client') {
                if (uri.query) {
                    server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
                    server = uri.query.server || 'irc.anonnet.org';
                    nick = uri.query.nick || '';
                    debug = (uri.query.debug !== '');
                } else {
                    server_set = false;
                    server = 'irc.anonnet.org';
                    nick = '';
                }
            } else {
                server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
                server = server_set ? uri_parts[2] : 'irc.anonnet.org';
                nick = uri.query.nick || '';
            }

            response.setHeader('X-Generated-By', 'KiwiIRC');
            hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f').update(debug ? 't' : 'f').update(server_set ? 't' : 'f').update(server).update(nick).update(agent).update(JSON.stringify(kiwi.config)).digest('base64');
            if (kiwi.cache.html[hash]) {
                if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
                    response.statusCode = 304;
                } else {
                    response.setHeader('Etag', kiwi.cache.html[hash].hash);
                    response.write(kiwi.cache.html[hash].html);
                }
                response.end();
            } else {
                kiwi.jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config }}, function (err, html) {
                    if (!err) {
                        var hash2 = crypto.createHash('md5').update(html).digest('base64');
                        kiwi.cache.html[hash] = {"html": html, "hash": hash2};
                        if (request.headers['if-none-match'] === hash2) {
                            response.statusCode = 304;
                        } else {
                            response.setHeader('Etag', hash2);
                            response.write(html);
                        }
                    } else {
                        response.statusCode = 500;
                    }
                    response.end();
                });
            }
        } else if (uri.pathname.substr(0, 10) === '/socket.io') {
            return;
        } else {
            response.statusCode = 404;
            response.end();
        }
    }
};




this.websocketListen = function (port, host, handler, secure, key, cert) {
    if (kiwi.httpServer) {
        kiwi.httpServer.close();
    }
    if (secure) {
        kiwi.httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + key), cert: fs.readFileSync(__dirname + '/' + cert)}, handler);
        kiwi.io = ws.listen(kiwi.httpServer, {secure: true});
        kiwi.httpServer.listen(port, host);
    } else {
        kiwi.httpServer = http.createServer(handler);
        kiwi.io = ws.listen(kiwi.httpServer, {secure: false});
        kiwi.httpServer.listen(port, host);
    }

    kiwi.io.set('log level', 1);
    kiwi.io.enable('browser client minification');
    kiwi.io.enable('browser client etag');
    kiwi.io.set('transports', kiwi.config.transports);

    kiwi.io.of('/kiwi').authorization(function (handshakeData, callback) {
        var address = handshakeData.address.address;
        
        if (typeof kiwi.connections[address] === 'undefined') {
            kiwi.connections[address] = {count: 0, sockets: []};
        }
        callback(null, true);
    }).on('connection', kiwi.websocketConnection);
};






this.websocketConnection = function (websocket) {
    var con;
    websocket.kiwi = {address: websocket.handshake.address.address};
    con = kiwi.connections[websocket.kiwi.address];

    if (con.count >= kiwi.config.max_client_conns) {
        websocket.emit('too_many_connections');
        websocket.disconnect();
    } else {
        con.count += 1;
        con.sockets.push(websocket);

        websocket.sendClientEvent = function (event_name, data) {
            var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
            if(ev === null) return;

            data.event = event_name;
            websocket.emit('message', data);
        };

        websocket.sendServerLine = function (data, eol) {
            eol = (typeof eol === 'undefined') ? '\r\n' : eol;
            websocket.ircSocket.write(data + eol);
        };

        websocket.on('irc connect', kiwi.websocketIRCConnect);
        websocket.on('message', kiwi.websocketMessage);
        websocket.on('disconnect', kiwi.websocketDisconnect);
    }
};





this.websocketIRCConnect = function (websocket, nick, host, port, ssl, callback) {
    var ircSocket;
    //setup IRC connection
    if (!ssl) {
        ircSocket = net.createConnection(port, host);
    } else {
        ircSocket = tls.connect(port, host);
    }
    ircSocket.setEncoding('ascii');
    ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
    ircSocket.on('error', function (e) {
        if (ircSocket.IRC.registered) {
            websocket.emit('disconnect');
        } else {
            websocket.emit('error', e.message);
        }
    });
    websocket.ircSocket = ircSocket;
    ircSocket.holdLast = false;
    ircSocket.held = '';
    
    ircSocket.on('data', function (data) {
        kiwi.ircSocketDataHandler(data, websocket, ircSocket);
    });
    
    ircSocket.IRC.nick = nick;
    // Send the login data
    dns.reverse(websocket.kiwi.address, function (err, domains) {
        //console.log(domains);
        websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
        if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
            websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
        }
        websocket.sendServerLine('CAP LS');
        websocket.sendServerLine('NICK ' + nick);
        websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);

        if ((callback) && (typeof (callback) === 'function')) {
            callback();
        }
    });
};



this.websocketMessage = function (websocket, msg, callback) {
    var args, obj;
    try {
        msg.data = JSON.parse(msg.data);
        args = msg.data.args;
        switch (msg.data.method) {
        case 'msg':
            if ((args.target) && (args.msg)) {
                obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
                if (obj !== null) {
                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg);
                }
            }
            break;
        case 'action':
            if ((args.target) && (args.msg)) {
                websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
            }
            break;
        case 'raw':
            websocket.sendServerLine(args.data);
            break;
        case 'join':
            if (args.channel) {
                _.each(args.channel.split(","), function (chan) {
                    websocket.sendServerLine('JOIN ' + chan);
                });
            }
            break;
        case 'topic':
            if (args.channel) {
                websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic);
            }
            break;
        case 'quit':
            websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
            websocket.sentQUIT = true;
            websocket.ircSocket.destroySoon();
            websocket.disconnect();
            break;
        case 'notice':<commit_msg>Removed non-printable chars from the source which effect CTCP ACTION<commit_after>var tls = null;
var net = null;
var http = null;
var https = null;
var fs = null;
var url = null;
var dns = null;
var crypto = null;
var ws = null;
var jsp = null;
var pro = null;
var _ = null;
var starttls = null;
var kiwi = null;

this.init = function (objs) {
	tls = objs.tls;
	net = objs.net;
	http = objs.http;
	https = objs.https;
	fs = objs.fs;
	url = objs.url;
	dns = objs.dns;
	crypto = objs.crypto;
	ws = objs.ws;
	jsp = objs.jsp;
	pro = objs.pro;
	_ = objs._;
	starttls = objs.starttls;
	kiwi = require('./kiwi.js');
}






/*
 * Some process changes
 */
this.setTitle = function () {
	process.title = 'kiwiirc';
}

this.changeUser = function () {
    if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
        try {
            process.setgid(kiwi.config.group);
        } catch (err) {
            console.log('Failed to set gid: ' + err);
            process.exit();
        }
    }

    if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
        try {
            process.setuid(kiwi.config.user);
        } catch (e) {
            console.log('Failed to set uid: ' + e);
            process.exit();
        }
    }
};









var ircNumerics = {
    RPL_WELCOME:            '001',
    RPL_MYINFO:             '004',
    RPL_ISUPPORT:           '005',
    RPL_WHOISUSER:          '311',
    RPL_WHOISSERVER:        '312',
    RPL_WHOISOPERATOR:      '313',
    RPL_WHOISIDLE:          '317',
    RPL_ENDOFWHOIS:         '318',
    RPL_WHOISCHANNELS:      '319',
    RPL_NOTOPIC:            '331',
    RPL_TOPIC:              '332',
    RPL_NAMEREPLY:          '353',
    RPL_ENDOFNAMES:         '366',
    RPL_MOTD:               '372',
    RPL_WHOISMODES:         '379',
    ERR_NOSUCHNICK:         '401',
    ERR_CANNOTSENDTOCHAN:   '404',
    ERR_TOOMANYCHANNELS:    '405',
    ERR_NICKNAMEINUSE:      '433',
    ERR_USERNOTINCHANNEL:   '441',
    ERR_NOTONCHANNEL:       '442',
    ERR_NOTREGISTERED:      '451',
    ERR_LINKCHANNEL:        '470',
    ERR_CHANNELISFULL:      '471',
    ERR_INVITEONLYCHAN:     '473',
    ERR_BANNEDFROMCHAN:     '474',
    ERR_BADCHANNELKEY:      '475',
    ERR_CHANOPRIVSNEEDED:   '482',
    RPL_STARTTLS:           '670'
};



this.parseIRCMessage = function (websocket, ircSocket, data) {
    /*global ircSocketDataHandler */
    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj;
    //regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i;
    //regex = /^(?::(\S+) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
    regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;

    msg = regex.exec(data);
    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() : ''
        };
        
        switch (msg.command.toUpperCase()) {
        case 'PING':
            websocket.sendServerLine('PONG ' + msg.trailing);
            break;
        case ircNumerics.RPL_WELCOME:
            if (ircSocket.IRC.CAP.negotiating) {
                ircSocket.IRC.CAP.negotiating = false;
                ircSocket.IRC.CAP.enabled = [];
                ircSocket.IRC.CAP.requested = [];
                ircSocket.IRC.registered = true;
            }
            //regex = /([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)/i;
            //matches = regex.exec(msg.trailing);
            nick =  msg.params.split(' ')[0];
            websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
            break;
        case ircNumerics.RPL_ISUPPORT:
            opts = msg.params.split(" ");
            options = [];
            for (i = 0; i < opts.length; i++) {
                opt = opts[i].split("=", 2);
                opt[0] = opt[0].toUpperCase();
                ircSocket.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
                if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
                    if (opt[0] === 'PREFIX') {
                        regex = /\(([^)]*)\)(.*)/;
                        matches = regex.exec(opt[1]);
                        if ((matches) && (matches.length === 3)) {
                            ircSocket.IRC.options[opt[0]] = [];
                            for (j = 0; j < matches[2].length; j++) {
                                //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
                                ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
                            }

                        }
                    }
                }
            }

            websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
            break;
        case ircNumerics.RPL_WHOISUSER:
        case ircNumerics.RPL_WHOISSERVER:
        case ircNumerics.RPL_WHOISOPERATOR:
        case ircNumerics.RPL_ENDOFWHOIS:
        case ircNumerics.RPL_WHOISCHANNELS:
        case ircNumerics.RPL_WHOISMODES:
            websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
            break;
        case ircNumerics.RPL_WHOISIDLE:
            params = msg.params.split(" ", 4);
            rtn = {server: '', nick: params[1], idle: params[2]};
            if (params[3]) {
                rtn.logon = params[3];
            }
            websocket.sendClientEvent('whois', rtn);
            break;
        case ircNumerics.RPL_MOTD:
            websocket.sendClientEvent('motd', {server: '', "msg": msg.trailing});
            break;
        case ircNumerics.RPL_NAMEREPLY:
            params = msg.params.split(" ");
            nick = params[0];
            chan = params[2];
            users = msg.trailing.split(" ");
            prefixes = _.values(ircSocket.IRC.options.PREFIX);
            nicklist = {};
            i = 0;
            _.each(users, function (user) {
                if (_.include(prefix, user.charAt(0))) {
                    prefix = user.charAt(0);
                    user = user.substring(1);
                    nicklist[user] = prefix;
                } else {
                    nicklist[user] = '';
                }
                if (i++ >= 50) {
                    websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
                    nicklist = {};
                    i = 0;
                }
            });
            if (i > 0) {
                websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
            } else {
                console.log("oops");
            }
            break;
        case ircNumerics.RPL_ENDOFNAMES:
            websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
            break;
        case ircNumerics.ERR_LINKCHANNEL:
            params = msg.params.split(" "); 
            websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
            break;
        case ircNumerics.ERR_NOSUCHNICK:
            websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case 'JOIN':
            // Some BNC's send malformed JOIN causing the channel to be as a
            // parameter instead of trailing.
            if (typeof msg.trailing === 'string' && msg.trailing !== '') {
                channel = msg.trailing;
            } else if (typeof msg.params === 'string' && msg.params !== '') {
                channel = msg.params;
            }

            websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
            if (msg.nick === ircSocket.IRC.nick) {
                websocket.sendServerLine('NAMES ' + msg.trailing);
            }
            break;
        case 'PART':
            websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
            break;
        case 'KICK':
            params = msg.params.split(" ");
            websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
            break;
        case 'QUIT':
            websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
            break;
        case 'NOTICE':
            if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
                // It's a CTCP response
                websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
            } else {
                websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
            }
            break;
        case 'NICK':
            websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
            break;
        case 'TOPIC':
            obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
            websocket.sendClientEvent('topic', obj);
            break;
        case ircNumerics.RPL_TOPIC:
            obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
            websocket.sendClientEvent('topic', obj);
            break;
        case ircNumerics.RPL_NOTOPIC:
            obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
            websocket.sendClientEvent('topic', obj);
            break;
        case 'MODE':
            opts = msg.params.split(" ");
            params = {nick: msg.nick};
            switch (opts.length) {
            case 1:
                params.effected_nick = opts[0];
                params.mode = msg.trailing;
                break;
            case 2:
                params.channel = opts[0];
                params.mode = opts[1];
                break;
            default:
                params.channel = opts[0];
                params.mode = opts[1];
                params.effected_nick = opts[2];
                break;
            }
            websocket.sendClientEvent('mode', params);
            break;
        case 'PRIVMSG':
            if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
                // It's a CTCP request
                if (msg.trailing.substr(1, 6) === 'ACTION') {
                    websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)});
                } else if (msg.trailing.substr(1, 7) === 'VERSION') {
                    ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
                } else {
                    websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
                }
            } else {
                obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
                websocket.sendClientEvent('msg', obj);
            }
            break;
        case 'CAP':
            caps = kiwi.config.cap_options;
            options = msg.trailing.split(" ");
            switch (_.last(msg.params.split(" "))) {
            case 'LS':
                opts = '';
                _.each(_.intersect(caps, options), function (cap) {
                    if (opts !== '') {
                        opts += " ";
                    }
                    opts += cap;
                    ircSocket.IRC.CAP.requested.push(cap);
                });
                if (opts.length > 0) {
                    websocket.sendServerLine('CAP REQ :' + opts);
                } else {
                    websocket.sendServerLine('CAP END');
                }
                // TLS is special
                /*if (_.include(options, 'tls')) {
                    websocket.sendServerLine('STARTTLS');
                    ircSocket.IRC.CAP.requested.push('tls');
                }*/
                break;
            case 'ACK':
                _.each(options, function (cap) {
                    ircSocket.IRC.CAP.enabled.push(cap);
                });
                if (_.last(msg.params.split(" ")) !== '*') {
                    ircSocket.IRC.CAP.requested = [];
                    ircSocket.IRC.CAP.negotiating = false;
                    websocket.sendServerLine('CAP END');
                }
                break;
            case 'NAK':
                ircSocket.IRC.CAP.requested = [];
                ircSocket.IRC.CAP.negotiating = false;
                websocket.sendServerLine('CAP END');
                break;
            }
            break;
        /*case ircNumerics.RPL_STARTTLS:
            try {
                IRC = ircSocket.IRC;
                listeners = ircSocket.listeners('data');
                ircSocket.removeAllListeners('data');
                ssl_socket = starttls(ircSocket, {}, function () {
                    ssl_socket.on("data", function (data) {
                        ircSocketDataHandler(data, websocket, ssl_socket);
                    });
                    ircSocket = ssl_socket;
                    ircSocket.IRC = IRC;
                    _.each(listeners, function (listener) {
                        ircSocket.addListener('data', listener);
                    });
                });
                //console.log(ircSocket);
            } catch (e) {
                console.log(e);
            }
            break;*/
        case ircNumerics.ERR_CANNOTSENDTOCHAN:
            websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_TOOMANYCHANNELS:
            websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_USERNOTINCHANNEL:
            params = msg.params.split(" ");
            websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
            break;
        case ircNumerics.ERR_NOTONCHANNEL:
            websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_CHANNELISFULL:
            websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_INVITEONLYCHAN:
            websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_BANNEDFROMCHAN:
            websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_BADCHANNELKEY:
            websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_CHANOPRIVSNEEDED:
            websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
            break;
        case ircNumerics.ERR_NICKNAMEINUSE:
            websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
            break;
        case 'ERROR':
            ircSocket.end();
            websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
            websocket.disconnect();
            break;
        case ircNumerics.ERR_NOTREGISTERED:
            if (ircSocket.IRC.registered) {
                console.log('Kiwi thinks user is registered, but the IRC server thinks differently');
            }
            break;
        default:
            console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
        }
    } else {
        console.log("Malformed IRC line: " + data);
    }
};






/*
 * NOTE: Some IRC servers or BNC's out there incorrectly use
 * only \n as a line splitter.
 */
this.ircSocketDataHandler = function (data, websocket, ircSocket) {
    var i;
    if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
        data = ircSocket.held + data;
        ircSocket.holdLast = false;
        ircSocket.held = '';
    }
    if (data.substr(-1) !== '\n') {
        ircSocket.holdLast = true;
    }
    data = data.split("\n");         
    for (i = 0; i < data.length; i++) {
        if (data[i]) {
            if ((ircSocket.holdLast) && (i === data.length - 1)) {
                ircSocket.held = data[i];
                break;
            }

            // We have a complete line of data, parse it!
            kiwi.parseIRCMessage(websocket, ircSocket, data[i].replace(/^\r+|\r+$/, ''));
        }
    }
};





this.httpHandler = function (request, response) {
    var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
        min = {}, public_http_path;
    if (kiwi.config.handle_http) {
        uri = url.parse(request.url, true);
        uri_parts = uri.pathname.split('/');
        
        subs = uri.pathname.substr(0, 4);
        if (uri.pathname === '/js/all.js') {
            if (kiwi.cache.alljs === '') {
                public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;

                min.util = fs.readFileSync(public_http_path + 'js/util.js');
                min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
                min.front = fs.readFileSync(public_http_path + 'js/front.js');
                min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
                min.ast = jsp.parse(min.util + min.gateway + min.front + min.iscroll);
                min.ast = pro.ast_mangle(min.ast);
                min.ast = pro.ast_squeeze(min.ast);
                min.final_code = pro.gen_code(min.ast);
                kiwi.cache.alljs = min.final_code;
                hash = crypto.createHash('md5').update(kiwi.cache.alljs);
                kiwi.cache.alljs_hash = hash.digest('base64');
            }
            if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
                response.statusCode = 304;
            } else {
                response.setHeader('ETag', kiwi.cache.alljs_hash);
                response.write(kiwi.cache.alljs);
            }
            response.end();
        } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
            request.addListener('end', function () {
                kiwi.fileServer.serve(request, response);
            });
        } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
            useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
            if (useragent.match(/android/i) !== -1) {
                agent = 'android';
                touchscreen = true;
            } else if (useragent.match(/iphone/) !== -1) {
                agent = 'iphone';
                touchscreen = true;
            } else if (useragent.match(/ipad/) !== -1) {
                agent = 'ipad';
                touchscreen = true;
            } else if (useragent.match(/ipod/) !== -1) {
                agent = 'ipod';
                touchscreen = true;
            } else {
                agent = 'normal';
                touchscreen = false;
            }
            agent = 'normal';
            touchscreen = false;
            
            if (uri_parts[1] !== 'client') {
                if (uri.query) {
                    server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
                    server = uri.query.server || 'irc.anonnet.org';
                    nick = uri.query.nick || '';
                    debug = (uri.query.debug !== '');
                } else {
                    server_set = false;
                    server = 'irc.anonnet.org';
                    nick = '';
                }
            } else {
                server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
                server = server_set ? uri_parts[2] : 'irc.anonnet.org';
                nick = uri.query.nick || '';
            }

            response.setHeader('X-Generated-By', 'KiwiIRC');
            hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f').update(debug ? 't' : 'f').update(server_set ? 't' : 'f').update(server).update(nick).update(agent).update(JSON.stringify(kiwi.config)).digest('base64');
            if (kiwi.cache.html[hash]) {
                if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
                    response.statusCode = 304;
                } else {
                    response.setHeader('Etag', kiwi.cache.html[hash].hash);
                    response.write(kiwi.cache.html[hash].html);
                }
                response.end();
            } else {
                kiwi.jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config }}, function (err, html) {
                    if (!err) {
                        var hash2 = crypto.createHash('md5').update(html).digest('base64');
                        kiwi.cache.html[hash] = {"html": html, "hash": hash2};
                        if (request.headers['if-none-match'] === hash2) {
                            response.statusCode = 304;
                        } else {
                            response.setHeader('Etag', hash2);
                            response.write(html);
                        }
                    } else {
                        response.statusCode = 500;
                    }
                    response.end();
                });
            }
        } else if (uri.pathname.substr(0, 10) === '/socket.io') {
            return;
        } else {
            response.statusCode = 404;
            response.end();
        }
    }
};




this.websocketListen = function (port, host, handler, secure, key, cert) {
    if (kiwi.httpServer) {
        kiwi.httpServer.close();
    }
    if (secure) {
        kiwi.httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + key), cert: fs.readFileSync(__dirname + '/' + cert)}, handler);
        kiwi.io = ws.listen(kiwi.httpServer, {secure: true});
        kiwi.httpServer.listen(port, host);
    } else {
        kiwi.httpServer = http.createServer(handler);
        kiwi.io = ws.listen(kiwi.httpServer, {secure: false});
        kiwi.httpServer.listen(port, host);
    }

    kiwi.io.set('log level', 1);
    kiwi.io.enable('browser client minification');
    kiwi.io.enable('browser client etag');
    kiwi.io.set('transports', kiwi.config.transports);

    kiwi.io.of('/kiwi').authorization(function (handshakeData, callback) {
        var address = handshakeData.address.address;
        
        if (typeof kiwi.connections[address] === 'undefined') {
            kiwi.connections[address] = {count: 0, sockets: []};
        }
        callback(null, true);
    }).on('connection', kiwi.websocketConnection);
};






this.websocketConnection = function (websocket) {
    var con;
    websocket.kiwi = {address: websocket.handshake.address.address};
    con = kiwi.connections[websocket.kiwi.address];

    if (con.count >= kiwi.config.max_client_conns) {
        websocket.emit('too_many_connections');
        websocket.disconnect();
    } else {
        con.count += 1;
        con.sockets.push(websocket);

        websocket.sendClientEvent = function (event_name, data) {
            var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
            if(ev === null) return;

            data.event = event_name;
            websocket.emit('message', data);
        };

        websocket.sendServerLine = function (data, eol) {
            eol = (typeof eol === 'undefined') ? '\r\n' : eol;
            websocket.ircSocket.write(data + eol);
        };

        websocket.on('irc connect', kiwi.websocketIRCConnect);
        websocket.on('message', kiwi.websocketMessage);
        websocket.on('disconnect', kiwi.websocketDisconnect);
    }
};





this.websocketIRCConnect = function (websocket, nick, host, port, ssl, callback) {
    var ircSocket;
    //setup IRC connection
    if (!ssl) {
        ircSocket = net.createConnection(port, host);
    } else {
        ircSocket = tls.connect(port, host);
    }
    ircSocket.setEncoding('ascii');
    ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
    ircSocket.on('error', function (e) {
        if (ircSocket.IRC.registered) {
            websocket.emit('disconnect');
        } else {
            websocket.emit('error', e.message);
        }
    });
    websocket.ircSocket = ircSocket;
    ircSocket.holdLast = false;
    ircSocket.held = '';
    
    ircSocket.on('data', function (data) {
        kiwi.ircSocketDataHandler(data, websocket, ircSocket);
    });
    
    ircSocket.IRC.nick = nick;
    // Send the login data
    dns.reverse(websocket.kiwi.address, function (err, domains) {
        //console.log(domains);
        websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
        if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
            websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
        }
        websocket.sendServerLine('CAP LS');
        websocket.sendServerLine('NICK ' + nick);
        websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);

        if ((callback) && (typeof (callback) === 'function')) {
            callback();
        }
    });
};



this.websocketMessage = function (websocket, msg, callback) {
    var args, obj;
    try {
        msg.data = JSON.parse(msg.data);
        args = msg.data.args;
        switch (msg.data.method) {
        case 'msg':
            if ((args.target) && (args.msg)) {
                obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
                if (obj !== null) {
                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg);
                }
            }
            break;
        case 'action':
            if ((args.target) && (args.msg)) {
                websocket.sendServerLine('PRIVMSG ' + args.target + ' :\001ACTION ' + args.msg + '\001');
            }
            break;
        case 'raw':
            websocket.sendServerLine(args.data);
            break;
        case 'join':
            if (args.channel) {
                _.each(args.channel.split(","), function (chan) {
                    websocket.sendServerLine('JOIN ' + chan);
                });
            }
            break;
        case 'topic':
            if (args.channel) {
                websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic);
            }
            break;
        case 'quit':
            websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
            websocket.sentQUIT = true;
            websocket.ircSocket.destroySoon();
            websocket.disconnect();
            break;
        case 'notice':
{'fraction_non_alphanumeric': 0.15461693688678588, 'fraction_numerical': 0.009915802658130957, 'mean_word_length': 1.707087355780551, 'pattern_counts': {'":': 26, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3565934', 'n_tokens_mistral': 18316, 'n_tokens_neox': 17270, 'n_words': 3143}