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