Updated Underscore.js, Tabview icons, Invalid next event bugfix, General CSS'ing...
[KiwiIRC.git] / node / app.js
CommitLineData
80c584e7 1/*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
2fc64ec2 2/*globals kiwi_root */
f236196a 3/* Fuck you, git. */
fd779420
D
4var tls = null;
5var net = null;
6var http = null;
7var https = null;
8var fs = null;
9var url = null;
10var dns = null;
11var crypto = null;
12var ws = null;
13var jsp = null;
14var pro = null;
15var _ = null;
16var starttls = null;
17var kiwi = null;
18
19this.init = function (objs) {
1fce4b40
JA
20 tls = objs.tls;
21 net = objs.net;
22 http = objs.http;
23 https = objs.https;
24 fs = objs.fs;
25 url = objs.url;
26 dns = objs.dns;
27 crypto = objs.crypto;
28 ws = objs.ws;
29 jsp = objs.jsp;
30 pro = objs.pro;
31 _ = objs._;
32 starttls = objs.starttls;
33 kiwi = require('./kiwi.js');
2fc64ec2 34};
fd779420
D
35
36
37
38
39
40
41/*
42 * Some process changes
43 */
44this.setTitle = function () {
1fce4b40 45 process.title = 'kiwiirc';
2fc64ec2 46};
fd779420
D
47
48this.changeUser = function () {
49 if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
50 try {
51 process.setgid(kiwi.config.group);
52 } catch (err) {
53 console.log('Failed to set gid: ' + err);
54 process.exit();
55 }
56 }
57
58 if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
59 try {
60 process.setuid(kiwi.config.user);
61 } catch (e) {
62 console.log('Failed to set uid: ' + e);
63 process.exit();
64 }
65 }
66};
67
68
69
70
71
72
73
74
75
76var ircNumerics = {
77 RPL_WELCOME: '001',
337c9866 78 RPL_MYINFO: '004',
fd779420
D
79 RPL_ISUPPORT: '005',
80 RPL_WHOISUSER: '311',
81 RPL_WHOISSERVER: '312',
82 RPL_WHOISOPERATOR: '313',
83 RPL_WHOISIDLE: '317',
84 RPL_ENDOFWHOIS: '318',
85 RPL_WHOISCHANNELS: '319',
b10afa2d
D
86 RPL_LISTSTART: '321',
87 RPL_LIST: '322',
88 RPL_LISTEND: '323',
fd779420
D
89 RPL_NOTOPIC: '331',
90 RPL_TOPIC: '332',
91 RPL_NAMEREPLY: '353',
92 RPL_ENDOFNAMES: '366',
93 RPL_MOTD: '372',
94 RPL_WHOISMODES: '379',
95 ERR_NOSUCHNICK: '401',
96 ERR_CANNOTSENDTOCHAN: '404',
97 ERR_TOOMANYCHANNELS: '405',
98 ERR_NICKNAMEINUSE: '433',
99 ERR_USERNOTINCHANNEL: '441',
100 ERR_NOTONCHANNEL: '442',
101 ERR_NOTREGISTERED: '451',
102 ERR_LINKCHANNEL: '470',
103 ERR_CHANNELISFULL: '471',
104 ERR_INVITEONLYCHAN: '473',
105 ERR_BANNEDFROMCHAN: '474',
106 ERR_BADCHANNELKEY: '475',
107 ERR_CHANOPRIVSNEEDED: '482',
108 RPL_STARTTLS: '670'
109};
110
111
112
113this.parseIRCMessage = function (websocket, ircSocket, data) {
114 /*global ircSocketDataHandler */
8343584e 115 var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, nicklist, caps, rtn, obj, tmp, namespace;
337c9866
D
116 //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;
117 //regex = /^(?::(\S+) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
118 regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
119
fd779420
D
120 msg = regex.exec(data);
121 if (msg) {
122 msg = {
123 prefix: msg[1],
124 nick: msg[2],
125 ident: msg[3],
126 hostname: msg[4] || '',
127 command: msg[5],
128 params: msg[6] || '',
129 trailing: (msg[7]) ? msg[7].trim() : ''
130 };
8343584e 131
fd779420
D
132 switch (msg.command.toUpperCase()) {
133 case 'PING':
134 websocket.sendServerLine('PONG ' + msg.trailing);
135 break;
136 case ircNumerics.RPL_WELCOME:
137 if (ircSocket.IRC.CAP.negotiating) {
138 ircSocket.IRC.CAP.negotiating = false;
139 ircSocket.IRC.CAP.enabled = [];
140 ircSocket.IRC.CAP.requested = [];
141 ircSocket.IRC.registered = true;
142 }
b73cccbd
D
143 //regex = /([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)/i;
144 //matches = regex.exec(msg.trailing);
145 nick = msg.params.split(' ')[0];
146 websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
fd779420
D
147 break;
148 case ircNumerics.RPL_ISUPPORT:
149 opts = msg.params.split(" ");
150 options = [];
151 for (i = 0; i < opts.length; i++) {
152 opt = opts[i].split("=", 2);
153 opt[0] = opt[0].toUpperCase();
337c9866 154 ircSocket.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
8343584e 155 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt[0])) {
fd779420
D
156 if (opt[0] === 'PREFIX') {
157 regex = /\(([^)]*)\)(.*)/;
158 matches = regex.exec(opt[1]);
159 if ((matches) && (matches.length === 3)) {
160 ircSocket.IRC.options[opt[0]] = [];
161 for (j = 0; j < matches[2].length; j++) {
162 //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
163 ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
fd779420 164 }
337c9866 165
fd779420
D
166 }
167 }
8343584e
D
168 if (opt[0] === 'NAMESX') {
169 websocket.sendServerLine('PROTOCTL NAMESX');
170 }
fd779420
D
171 }
172 }
337c9866 173
fd779420
D
174 websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
175 break;
176 case ircNumerics.RPL_WHOISUSER:
177 case ircNumerics.RPL_WHOISSERVER:
178 case ircNumerics.RPL_WHOISOPERATOR:
179 case ircNumerics.RPL_ENDOFWHOIS:
180 case ircNumerics.RPL_WHOISCHANNELS:
181 case ircNumerics.RPL_WHOISMODES:
182 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
183 break;
b10afa2d
D
184
185 case ircNumerics.RPL_LISTSTART:
186 (function () {
187 websocket.sendClientEvent('list_start', {server: ''});
5c7ac96f 188 websocket.kiwi.buffer.list = [];
b10afa2d
D
189 }());
190 break;
191 case ircNumerics.RPL_LISTEND:
192 (function () {
5c7ac96f
JA
193 if (websocket.kiwi.buffer.list.length > 0) {
194 websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
195 return channel.num_users;
196 });
197 websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
198 websocket.kiwi.buffer.list = [];
199 }
b10afa2d
D
200 websocket.sendClientEvent('list_end', {server: ''});
201 }());
202 break;
8343584e 203
b10afa2d
D
204 case ircNumerics.RPL_LIST:
205 (function () {
206 var parts, channel, num_users, modes, topic;
207
208 parts = msg.params.split(' ');
209 channel = parts[1];
210 num_users = parts[2];
211 modes = msg.trailing.split(' ', 1);
80c584e7 212 topic = msg.trailing.substring(msg.trailing.indexOf(' ') + 1);
b10afa2d 213
5c7ac96f
JA
214 //websocket.sendClientEvent('list_channel', {
215 websocket.kiwi.buffer.list.push({
b10afa2d
D
216 server: '',
217 channel: channel,
218 topic: topic,
219 modes: modes,
5c7ac96f 220 num_users: parseInt(num_users, 10)
b10afa2d 221 });
8343584e 222
5c7ac96f
JA
223 if (websocket.kiwi.buffer.list.length > 200) {
224 websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
225 return channel.num_users;
226 });
227 websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
228 websocket.kiwi.buffer.list = [];
229 }
8343584e 230
b10afa2d
D
231 }());
232 break;
233
fd779420
D
234 case ircNumerics.RPL_WHOISIDLE:
235 params = msg.params.split(" ", 4);
236 rtn = {server: '', nick: params[1], idle: params[2]};
237 if (params[3]) {
238 rtn.logon = params[3];
239 }
240 websocket.sendClientEvent('whois', rtn);
241 break;
242 case ircNumerics.RPL_MOTD:
243 websocket.sendClientEvent('motd', {server: '', "msg": msg.trailing});
244 break;
245 case ircNumerics.RPL_NAMEREPLY:
246 params = msg.params.split(" ");
247 nick = params[0];
248 chan = params[2];
249 users = msg.trailing.split(" ");
8343584e 250 nicklist = [];
fd779420
D
251 i = 0;
252 _.each(users, function (user) {
8343584e
D
253 var j, k, modes = [];
254 for (j = 0; j < user.length; j++) {
255 for (k = 0; k < ircSocket.IRC.options.PREFIX.length; k++) {
256 if (user.charAt(j) === ircSocket.IRC.options.PREFIX[k].symbol) {
257 modes.push(ircSocket.IRC.options.PREFIX[k].mode);
258 }
259 }
fd779420 260 }
8343584e 261 nicklist.push({nick: user, modes: modes});
fd779420
D
262 if (i++ >= 50) {
263 websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
8343584e 264 nicklist = [];
fd779420
D
265 i = 0;
266 }
267 });
268 if (i > 0) {
269 websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
270 } else {
271 console.log("oops");
272 }
273 break;
274 case ircNumerics.RPL_ENDOFNAMES:
275 websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
276 break;
277 case ircNumerics.ERR_LINKCHANNEL:
8343584e 278 params = msg.params.split(" ");
fd779420
D
279 websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
280 break;
281 case ircNumerics.ERR_NOSUCHNICK:
282 websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
283 break;
284 case 'JOIN':
285 // Some BNC's send malformed JOIN causing the channel to be as a
286 // parameter instead of trailing.
287 if (typeof msg.trailing === 'string' && msg.trailing !== '') {
288 channel = msg.trailing;
289 } else if (typeof msg.params === 'string' && msg.params !== '') {
290 channel = msg.params;
291 }
292
293 websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
294 if (msg.nick === ircSocket.IRC.nick) {
295 websocket.sendServerLine('NAMES ' + msg.trailing);
296 }
297 break;
298 case 'PART':
299 websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
300 break;
301 case 'KICK':
302 params = msg.params.split(" ");
303 websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
304 break;
305 case 'QUIT':
306 websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
307 break;
308 case 'NOTICE':
309 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
310 // It's a CTCP response
311 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)});
312 } else {
a5ffcf00 313 websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
fd779420
D
314 }
315 break;
316 case 'NICK':
317 websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
318 break;
319 case 'TOPIC':
320 obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
321 websocket.sendClientEvent('topic', obj);
322 break;
323 case ircNumerics.RPL_TOPIC:
324 obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
325 websocket.sendClientEvent('topic', obj);
326 break;
327 case ircNumerics.RPL_NOTOPIC:
328 obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
329 websocket.sendClientEvent('topic', obj);
330 break;
331 case 'MODE':
332 opts = msg.params.split(" ");
333 params = {nick: msg.nick};
334 switch (opts.length) {
335 case 1:
336 params.effected_nick = opts[0];
337 params.mode = msg.trailing;
338 break;
339 case 2:
340 params.channel = opts[0];
341 params.mode = opts[1];
342 break;
343 default:
344 params.channel = opts[0];
345 params.mode = opts[1];
346 params.effected_nick = opts[2];
347 break;
348 }
349 websocket.sendClientEvent('mode', params);
350 break;
351 case 'PRIVMSG':
352 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
353 // It's a CTCP request
354 if (msg.trailing.substr(1, 6) === 'ACTION') {
355 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)});
abb46d2d 356 } else if (msg.trailing.substr(1, 4) === 'KIWI') {
2fc64ec2
JA
357 tmp = msg.trailing.substr(6, msg.trailing.length - 2);
358 namespace = tmp.split(' ', 1)[0];
359 websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)});
8343584e 360
fd779420
D
361 } else if (msg.trailing.substr(1, 7) === 'VERSION') {
362 ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
363 } else {
364 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)});
365 }
366 } else {
367 obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
368 websocket.sendClientEvent('msg', obj);
369 }
370 break;
371 case 'CAP':
372 caps = kiwi.config.cap_options;
373 options = msg.trailing.split(" ");
374 switch (_.last(msg.params.split(" "))) {
375 case 'LS':
376 opts = '';
377 _.each(_.intersect(caps, options), function (cap) {
378 if (opts !== '') {
379 opts += " ";
380 }
381 opts += cap;
382 ircSocket.IRC.CAP.requested.push(cap);
383 });
384 if (opts.length > 0) {
385 websocket.sendServerLine('CAP REQ :' + opts);
386 } else {
387 websocket.sendServerLine('CAP END');
388 }
389 // TLS is special
390 /*if (_.include(options, 'tls')) {
391 websocket.sendServerLine('STARTTLS');
392 ircSocket.IRC.CAP.requested.push('tls');
393 }*/
394 break;
395 case 'ACK':
396 _.each(options, function (cap) {
397 ircSocket.IRC.CAP.enabled.push(cap);
398 });
399 if (_.last(msg.params.split(" ")) !== '*') {
400 ircSocket.IRC.CAP.requested = [];
401 ircSocket.IRC.CAP.negotiating = false;
402 websocket.sendServerLine('CAP END');
403 }
404 break;
405 case 'NAK':
406 ircSocket.IRC.CAP.requested = [];
407 ircSocket.IRC.CAP.negotiating = false;
408 websocket.sendServerLine('CAP END');
409 break;
410 }
411 break;
412 /*case ircNumerics.RPL_STARTTLS:
413 try {
414 IRC = ircSocket.IRC;
415 listeners = ircSocket.listeners('data');
416 ircSocket.removeAllListeners('data');
417 ssl_socket = starttls(ircSocket, {}, function () {
418 ssl_socket.on("data", function (data) {
419 ircSocketDataHandler(data, websocket, ssl_socket);
420 });
421 ircSocket = ssl_socket;
422 ircSocket.IRC = IRC;
423 _.each(listeners, function (listener) {
424 ircSocket.addListener('data', listener);
425 });
426 });
427 //console.log(ircSocket);
428 } catch (e) {
429 console.log(e);
430 }
431 break;*/
432 case ircNumerics.ERR_CANNOTSENDTOCHAN:
433 websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
434 break;
435 case ircNumerics.ERR_TOOMANYCHANNELS:
436 websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
437 break;
438 case ircNumerics.ERR_USERNOTINCHANNEL:
439 params = msg.params.split(" ");
440 websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
441 break;
442 case ircNumerics.ERR_NOTONCHANNEL:
443 websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
444 break;
445 case ircNumerics.ERR_CHANNELISFULL:
446 websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
447 break;
448 case ircNumerics.ERR_INVITEONLYCHAN:
449 websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
450 break;
451 case ircNumerics.ERR_BANNEDFROMCHAN:
452 websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
453 break;
454 case ircNumerics.ERR_BADCHANNELKEY:
455 websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
456 break;
457 case ircNumerics.ERR_CHANOPRIVSNEEDED:
458 websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
459 break;
460 case ircNumerics.ERR_NICKNAMEINUSE:
461 websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
462 break;
463 case 'ERROR':
464 ircSocket.end();
465 websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
466 websocket.disconnect();
467 break;
468 case ircNumerics.ERR_NOTREGISTERED:
469 if (ircSocket.IRC.registered) {
470 console.log('Kiwi thinks user is registered, but the IRC server thinks differently');
471 }
472 break;
473 default:
474 console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
475 }
476 } else {
d6e9bff7 477 console.log("Malformed IRC line: " + data);
fd779420
D
478 }
479};
480
481
482
483
484
485
486/*
487 * NOTE: Some IRC servers or BNC's out there incorrectly use
488 * only \n as a line splitter.
489 */
490this.ircSocketDataHandler = function (data, websocket, ircSocket) {
491 var i;
492 if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
493 data = ircSocket.held + data;
494 ircSocket.holdLast = false;
495 ircSocket.held = '';
496 }
497 if (data.substr(-1) !== '\n') {
498 ircSocket.holdLast = true;
499 }
8343584e 500 data = data.split("\n");
fd779420
D
501 for (i = 0; i < data.length; i++) {
502 if (data[i]) {
503 if ((ircSocket.holdLast) && (i === data.length - 1)) {
504 ircSocket.held = data[i];
505 break;
506 }
507
508 // We have a complete line of data, parse it!
509 kiwi.parseIRCMessage(websocket, ircSocket, data[i].replace(/^\r+|\r+$/, ''));
510 }
511 }
512};
513
514
515
516
517
518this.httpHandler = function (request, response) {
8397d902 519 var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
9f6954dc
JA
520 min = {}, public_http_path,
521 secure = (typeof request.client.encrypted === 'object');
0622748f
D
522
523 try {
524 if (kiwi.config.handle_http) {
525 uri = url.parse(request.url, true);
526 uri_parts = uri.pathname.split('/');
527
528 subs = uri.pathname.substr(0, 4);
529 if (uri.pathname === '/js/all.js') {
530 if (kiwi.cache.alljs === '') {
531 public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;
532
1fce4b40 533 min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js');
0622748f
D
534 min.util = fs.readFileSync(public_http_path + 'js/util.js');
535 min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
536 min.front = fs.readFileSync(public_http_path + 'js/front.js');
b41a381f
D
537 min.front_events = fs.readFileSync(public_http_path + 'js/front.events.js');
538 min.front_ui = fs.readFileSync(public_http_path + 'js/front.ui.js');
0622748f 539 min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
b41a381f 540 min.ast = jsp.parse(min.underscore + min.util + min.gateway + min.front + min.front_events + min.front_ui + min.iscroll);
0622748f
D
541 min.ast = pro.ast_mangle(min.ast);
542 min.ast = pro.ast_squeeze(min.ast);
543 min.final_code = pro.gen_code(min.ast);
544 kiwi.cache.alljs = min.final_code;
545 hash = crypto.createHash('md5').update(kiwi.cache.alljs);
546 kiwi.cache.alljs_hash = hash.digest('base64');
547 }
548 if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
549 response.statusCode = 304;
550 } else {
551 response.setHeader('Content-type', 'application/javascript');
552 response.setHeader('ETag', kiwi.cache.alljs_hash);
553 response.write(kiwi.cache.alljs);
554 }
555 response.end();
556 } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
557 request.addListener('end', function () {
558 kiwi.fileServer.serve(request, response);
559 });
560 } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
561 useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
562 if (useragent.match(/android/i) !== -1) {
563 agent = 'android';
564 touchscreen = true;
565 } else if (useragent.match(/iphone/) !== -1) {
566 agent = 'iphone';
567 touchscreen = true;
568 } else if (useragent.match(/ipad/) !== -1) {
569 agent = 'ipad';
570 touchscreen = true;
571 } else if (useragent.match(/ipod/) !== -1) {
572 agent = 'ipod';
573 touchscreen = true;
574 } else {
575 agent = 'normal';
576 touchscreen = false;
577 }
fd779420
D
578 agent = 'normal';
579 touchscreen = false;
91726016 580
0622748f 581 debug = (typeof uri.query.debug !== 'undefined');
8343584e 582
0622748f
D
583 if (uri_parts[1] !== 'client') {
584 if (uri.query) {
585 server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
586 server = uri.query.server || 'irc.anonnet.org';
587 nick = uri.query.nick || '';
588 } else {
589 server_set = false;
590 server = 'irc.anonnet.org';
591 nick = '';
592 }
8397d902 593 } else {
0622748f
D
594 server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
595 server = server_set ? uri_parts[2] : 'irc.anonnet.org';
596 nick = uri.query.nick || '';
8397d902 597 }
8397d902 598
0622748f
D
599 response.setHeader('X-Generated-By', 'KiwiIRC');
600 hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f')
601 .update(debug ? 't' : 'f')
602 .update(server_set ? 't' : 'f')
603 .update(secure ? 't' : 'f')
604 .update(server)
605 .update(nick)
606 .update(agent)
607 .update(JSON.stringify(kiwi.config))
608 .digest('base64');
609 if (kiwi.cache.html[hash]) {
610 if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
611 response.statusCode = 304;
fd779420 612 } else {
0622748f
D
613 response.setHeader('Etag', kiwi.cache.html[hash].hash);
614 response.setHeader('Content-type', 'text/html');
615 response.write(kiwi.cache.html[hash].html);
fd779420
D
616 }
617 response.end();
0622748f
D
618 } else {
619 fs.readFile(__dirname + '/client/index.html.jade', 'utf8', function (err, str) {
620 var html, hash2;
621 if (!err) {
622 html = kiwi.jade.compile(str)({ "touchscreen": touchscreen, "debug": debug, "secure": secure, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config });
623 hash2 = crypto.createHash('md5').update(html).digest('base64');
624 kiwi.cache.html[hash] = {"html": html, "hash": hash2};
625 if (request.headers['if-none-match'] === hash2) {
626 response.statusCode = 304;
627 } else {
628 response.setHeader('Etag', hash2);
629 response.setHeader('Content-type', 'text/html');
630 response.write(html);
631 }
632 } else {
633 response.statusCode = 500;
634 }
635 response.end();
636 });
637 }
638 } else if (uri.pathname.substr(0, 10) === '/socket.io') {
639 return;
640 } else {
641 response.statusCode = 404;
642 response.end();
fd779420 643 }
fd779420 644 }
0622748f
D
645
646 } catch (e) {
647 console.log('ERROR app.httpHandler()');
648 console.log(e);
fd779420
D
649 }
650};
651
652
653
654
9f6954dc
JA
655this.websocketListen = function (ports, host, handler, key, cert) {
656 if (kiwi.httpServers.length > 0) {
657 _.each(kiwi.httpServers, function (hs) {
658 hs.close();
659 });
660 kiwi.httpsServers = [];
fd779420 661 }
8343584e 662
9f6954dc
JA
663 _.each(ports, function (port) {
664 var hs;
665 if (port.secure === true) {
666 hs = https.createServer({key: fs.readFileSync(__dirname + '/' + key), cert: fs.readFileSync(__dirname + '/' + cert)}, handler);
667 kiwi.io.push(ws.listen(hs, {secure: true}));
668 hs.listen(port.number);
0622748f 669 console.log("Listening on %s:%d with SSL", host, port.number);
9f6954dc
JA
670 } else {
671 hs = http.createServer(handler);
672 kiwi.io.push(ws.listen(hs, {secure: false}));
673 hs.listen(port.number);
0622748f 674 console.log("Listening on %s:%d without SSL", host, port.number);
fd779420 675 }
9f6954dc 676 });
8343584e 677
9f6954dc
JA
678 _.each(kiwi.io, function (io) {
679 io.set('log level', 1);
680 io.enable('browser client minification');
681 io.enable('browser client etag');
682 io.set('transports', kiwi.config.transports);
1fce4b40 683
9f6954dc
JA
684 io.of('/kiwi').authorization(function (handshakeData, callback) {
685 var address = handshakeData.address.address;
686 if (typeof kiwi.connections[address] === 'undefined') {
687 kiwi.connections[address] = {count: 0, sockets: []};
688 }
689 callback(null, true);
690 }).on('connection', kiwi.websocketConnection);
691 });
fd779420
D
692};
693
694
695
696
697
698
699this.websocketConnection = function (websocket) {
700 var con;
9f6954dc 701 console.log("New connection!");
5c7ac96f 702 websocket.kiwi = {address: websocket.handshake.address.address, buffer: {list: []}};
fd779420
D
703 con = kiwi.connections[websocket.kiwi.address];
704
705 if (con.count >= kiwi.config.max_client_conns) {
706 websocket.emit('too_many_connections');
707 websocket.disconnect();
708 } else {
709 con.count += 1;
710 con.sockets.push(websocket);
711
712 websocket.sendClientEvent = function (event_name, data) {
4f06269b 713 var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
2fc64ec2
JA
714 if (ev === null) {
715 return;
716 }
4f06269b 717
fd779420
D
718 data.event = event_name;
719 websocket.emit('message', data);
720 };
721
722 websocket.sendServerLine = function (data, eol) {
723 eol = (typeof eol === 'undefined') ? '\r\n' : eol;
abb46d2d
D
724
725 try {
726 websocket.ircSocket.write(data + eol);
727 } catch (e) { }
fd779420
D
728 };
729
730 websocket.on('irc connect', kiwi.websocketIRCConnect);
731 websocket.on('message', kiwi.websocketMessage);
732 websocket.on('disconnect', kiwi.websocketDisconnect);
733 }
734};
735
736
737
738
739
740this.websocketIRCConnect = function (websocket, nick, host, port, ssl, callback) {
741 var ircSocket;
742 //setup IRC connection
743 if (!ssl) {
744 ircSocket = net.createConnection(port, host);
745 } else {
746 ircSocket = tls.connect(port, host);
747 }
748 ircSocket.setEncoding('ascii');
749 ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
750 ircSocket.on('error', function (e) {
751 if (ircSocket.IRC.registered) {
752 websocket.emit('disconnect');
753 } else {
754 websocket.emit('error', e.message);
755 }
756 });
757 websocket.ircSocket = ircSocket;
758 ircSocket.holdLast = false;
759 ircSocket.held = '';
8343584e 760
fd779420
D
761 ircSocket.on('data', function (data) {
762 kiwi.ircSocketDataHandler(data, websocket, ircSocket);
763 });
8343584e 764
fd779420
D
765 ircSocket.IRC.nick = nick;
766 // Send the login data
767 dns.reverse(websocket.kiwi.address, function (err, domains) {
768 //console.log(domains);
769 websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
770 if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
771 websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
772 }
773 websocket.sendServerLine('CAP LS');
774 websocket.sendServerLine('NICK ' + nick);
8397d902 775 websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
fd779420
D
776
777 if ((callback) && (typeof (callback) === 'function')) {
778 callback();
779 }
780 });
781};
782
783
784
785this.websocketMessage = function (websocket, msg, callback) {
786 var args, obj;
787 try {
788 msg.data = JSON.parse(msg.data);
789 args = msg.data.args;
790 switch (msg.data.method) {
791 case 'msg':
792 if ((args.target) && (args.msg)) {
793 obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
794 if (obj !== null) {
795 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg);
796 }
797 }
798 break;
799 case 'action':
800 if ((args.target) && (args.msg)) {
2fc64ec2 801 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
fd779420
D
802 }
803 break;
abb46d2d
D
804
805 case 'kiwi':
806 if ((args.target) && (args.data)) {
2fc64ec2 807 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1));
abb46d2d
D
808 }
809 break;
810
fd779420
D
811 case 'raw':
812 websocket.sendServerLine(args.data);
813 break;
814 case 'join':
815 if (args.channel) {
816 _.each(args.channel.split(","), function (chan) {
817 websocket.sendServerLine('JOIN ' + chan);
818 });
819 }
820 break;
821 case 'topic':
822 if (args.channel) {
823 websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic);
824 }
825 break;
826 case 'quit':
827 websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
828 websocket.sentQUIT = true;
829 websocket.ircSocket.destroySoon();
830 websocket.disconnect();
831 break;
832 case 'notice':
833 if ((args.target) && (args.msg)) {
834 websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg);
835 }
836 break;
837 default:
838 }
839 if ((callback) && (typeof (callback) === 'function')) {
840 callback();
841 }
842 } catch (e) {
843 console.log("Caught error: " + e);
844 }
845};
846
847
848
849this.websocketDisconnect = function (websocket) {
1fce4b40 850 var con;
fd779420
D
851
852 if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
853 try {
854 websocket.ircSocket.end('QUIT :' + kiwi.config.quit_message + '\r\n');
855 websocket.sentQUIT = true;
856 websocket.ircSocket.destroySoon();
857 } catch (e) {
858 }
859 }
860 con = kiwi.connections[websocket.kiwi.address];
861 con.count -= 1;
862 con.sockets = _.reject(con.sockets, function (sock) {
863 return sock === websocket;
864 });
865};
866
867
868
869
870
871
872this.rehash = function () {
873 var changes, i,
874 reload_config = kiwi.loadConfig();
875
876 // If loading the new config errored out, dont attempt any changes
877 if (reload_config === false) {
878 return false;
879 }
8343584e 880
fd779420
D
881 // We just want the settings that have been changed
882 changes = reload_config[1];
883
884 if (Object.keys(changes).length !== 0) {
885 console.log('%s config changes: \n', Object.keys(changes).length, changes);
886 for (i in changes) {
887 switch (i) {
9f6954dc 888 case 'ports':
fd779420 889 case 'bind_address':
fd779420
D
890 case 'ssl_key':
891 case 'ssl_cert':
9f6954dc
JA
892 kiwi.websocketListen(kiwi.config.ports, kiwi.config.bind_address, kiwi.httpHandler, kiwi.config.ssl_key, kiwi.config.ssl_cert);
893 delete changes.ports;
fd779420 894 delete changes.bind_address;
fd779420
D
895 delete changes.ssl_key;
896 delete changes.ssl_cert;
897 break;
898 case 'user':
899 case 'group':
900 kiwi.changeUser();
901 delete changes.user;
902 delete changes.group;
903 break;
904 case 'module_dir':
905 case 'modules':
906 kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config);
907 kiwi.kiwi_mod.printMods();
908 delete changes.module_dir;
909 delete changes.modules;
910 break;
911 }
912 }
913 }
914
915 // Also clear the kiwi.cached javascript and HTML
916 if (kiwi.config.handle_http) {
917 kiwi.cache = {alljs: '', html: []};
918 }
919
920 return true;
921};
922
923
924
925
926
927/*
928 * KiwiIRC controlling via STDIN
929 */
87a6abbe 930this.manageControll = function (data) {
2fc64ec2
JA
931 var parts = data.toString().trim().split(' '),
932 connections_cnt = 0,
933 i;
87a6abbe
D
934 switch (parts[0]) {
935 case 'rehash':
936 console.log('Rehashing...');
937 console.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
938 break;
939
1fce4b40 940 case 'recode':
87a6abbe
D
941 console.log('Recoding...');
942 console.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
943 break;
944
945 case 'mod':
946 if (parts[1] === 'reload') {
947 console.log('Reloading module (' + parts[2] + ')..');
948 kiwi.kiwi_mod.reloadModule(parts[2]);
949 }
950 break;
4f06269b 951
87a6abbe
D
952 case 'cache':
953 if (parts[1] === 'clear') {
954 kiwi.cache.html = {};
604c5174 955 kiwi.cache.alljs = '';
87a6abbe
D
956 console.log('HTML cache cleared');
957 }
958 break;
959
960 case 'status':
2fc64ec2 961 for (i in kiwi.connections) {
87a6abbe
D
962 connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
963 }
964 console.log(connections_cnt.toString() + ' connected clients');
965 break;
c89b9fdf 966
87a6abbe
D
967 default:
968 console.log('Unknown command \'' + parts[0] + '\'');
969 }
2fc64ec2 970};