const http = require("http") ,https = require("https") ,Url = require("./url.js").Url ,config = require("../config.js") ,sessionManager = require("./session.js").SessionManager ,IrcConnection = require("./ircServer/ircConnection.js").IrcConnection ,AccountController = require("./controller/accountController.js").AccountController ,ApiController = require("./controller/apiController.js").ApiController ,MultiChatManager = require("./multichatManager.js").MultiChatManager ,AccountManager = require("./models/accounts.js").accountManager ,Slack = require("./slack.js").Slack ,slackManager = require("./slackManager.js").SlackManager ,FaviconWriter = require("./faviconWriter.js").FaviconWriter; function Server() { var ctx = this, errSocks = [], ircSocks = [], lastSockId = 0; this.httpServ = http.createServer(function(req, res) { res.ended = false; res.once('end', () => { res.ended = true; }); ctx.onRequest(req, res); }); this.httpServ.on("connection", (sock) => { sock.id = ++lastSockId; sock.once('close', () => { let i = ircSocks.indexOf(sock.id); if (i !== -1) ircSocks.splice(i, 1); i = errSocks.indexOf(sock.id); if (i !== -1) errSocks.splice(i, 1); }); sock.on("data", (data) => { var dataStrArr = data.toString("utf-8").split(/\r?\n/g); for (var i =0, nbLines =dataStrArr.length; i < nbLines; i++) if ((/^(NICK|PASS) /).test(dataStrArr[i])) { var i = errSocks.indexOf(sock.id); if (i >= 0) { errSocks.splice(i, 1); ircSocks.push(sock.id); var con = new IrcConnection(sock); dataStrArr.forEach((str) => { con.parse(str); }); } break; } }); }); // Default behaviour is to destroy connection, we would like to switch to IRC protocol instead this.httpServ.removeAllListeners("clientError"); this.httpServ.on("clientError", (err, sock) => { if (ircSocks.indexOf(sock.id) === -1 && errSocks.indexOf(sock.id) === -1) { errSocks.push(sock.id); setTimeout(() => { if (errSocks.indexOf(sock.id) !== -1) { sock.destroy(err); } }, 1); } }); this.httpServ.listen(config.port, ctx.onListen); } Server.parseCookies = function(req) { var cookies = req.headers.cookie ,cookieObj = null; if (cookies) { cookieObj = {}; cookies = cookies.split(";"); for (var cookieIndex =0, nbCookies =cookies.length; cookieIndex < nbCookies; cookieIndex++) { var cookieParts = cookies[cookieIndex].indexOf("=") ,cookieKey = null ,cookieValue = null; if (cookieParts === -1) { cookieKey = cookies[cookieIndex].trim(); } else { cookieKey = cookies[cookieIndex].substr(0, cookieParts).trim(); cookieValue = cookies[cookieIndex].substr(cookieParts +1).trim(); } cookieObj[cookieKey] = cookieValue; } } return cookieObj; }; Server.stringifyCookies = function(cookies) { var cookieString = ""; for (var cookieKey in cookies) { cookieString += cookieString; if (cookies[cookieString] !== null) { cookieString += "="; } cookieString += "; "; } return cookieString; }; Server.prototype.onListen = function() { console.log("onListen"); }; Server.prototype.execTemplate = function(template, req, res) { var resp = template.exec(req, res, this); if (resp && resp.body) { if (resp.status) res.writeHeader(resp.status); res.end(resp.body); } // else something is running asynchronously, let template close request later.. }; Server.prototype.execRequest = function(req, res) { if (req.urlObj.isTemplate() && req.urlObj.template.needLogin === false) { return this.execTemplate(req.urlObj.template, req, res); } else if (req.urlObj.isPublic()) { if (!config.isDebug) res.setHeader('Cache-Control', 'private, max-age=' +15 * 60); res.setHeader('Content-Length', req.urlObj.serve.stat.size); res.writeHeader("200"); req.urlObj.getReadStream().pipe(res, { end: true }); sessionManager.saveSession(req.session); return; // async pipe will close when finished } else if (!req.account) { res.writeHeader("302", { Location: "login" }); res.end(); } else if (req.urlObj.isTemplate()) { return this.execTemplate(req.urlObj.template, req, res); } else if (req.urlObj.match(["favicon.png"])) { if (!config.isDebug) res.setHeader('Cache-Control', 'private, max-age=' +15 * 60); var unreadHi = (req.urlObj.queryTokens.h && req.urlObj.queryTokens.h[0]) ? parseInt(req.urlObj.queryTokens.h[0], 10) : 0 ,unread = (req.urlObj.queryTokens.m && req.urlObj.queryTokens.m[0]) ? parseInt(req.urlObj.queryTokens.m[0], 10) : 0; FaviconWriter.write(unreadHi, unread, (buf) => { res.writeHeader("200"); res.end(buf); }); sessionManager.saveSession(req.session); return; } else { // Api / dynamic content var apiSuccess = false; res.chatContext = new MultiChatManager(); var services = req.account.getServices(); for (var serviceId in services) { switch (services[serviceId].type) { case "Slack": res.chatContext.push(slackManager.lazyGet(serviceId, services[serviceId].oauthParam, req.reqT)); break; default: console.error("Unknown service type for ", services[serviceId], " with account #" +req.account.id); } } if (req.urlObj.urlParts[0] === "account") { AccountController.onRequest(req, res, this); } else if (req.urlObj.urlParts[0] === "api") { ApiController.onRequest(req, res, this); } else { this.execTemplate(require('./template/_404.js'), req, res); } } }; Server.prototype.onRequest = function(req, res) { req.reqT = Date.now(); req.cookies = Server.parseCookies(req); req.session = sessionManager.forRequest(req); req.urlObj = new Url(req.url); req.account = null; if (req.session) { var self = this; AccountManager.fromId(req.session.accountId, (acc) => { req.account = acc; self.execRequest(req, res); }); } else { this.execRequest(req, res); } }; module.exports.HttpServ = Server;