const http = require("http") ,https = require("https") ,clientSession = require("client-sessions") ,Url = require("./url.js").Url ,config = require("../config.js") ,sessionManager = require("./session.js").SessionManager ,MultiChatManager = require("./multichatManager.js").MultiChatManager ,Slack = require("./slack.js").Slack ,slackManager = require("./slackManager.js").SlackManager ,FaviconWriter = require("./faviconWriter.js").FaviconWriter; function Server(port) { var ctx = this; this.httpServ = http.createServer(function(req, res) { res.ended = false; res.on('end', () => { res.ended = true; }); ctx.onRequest(req, res); }); this.httpServ.listen(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"); }; function redirectToSlackAuth(res) { res.writeHeader("302", { Location: "https://slack.com/oauth/authorize" +"?client_id=" +config.clientId +"&scope=" +slackManager.getScope().join(",") +"&redirect_uri=" +config.rootUrl }); res.end(); } function recursiveGet(url, cb, redirectLoop) { var getFnc = http.get; if (url.substr(0, 8) === "https://") getFnc = https.get; getFnc(url, (d) => { if (d.statusCode >= 300 && d.statusCode < 400 && d.headers["location"]) { if (!redirectLoop) redirectLoop = []; if (redirectLoop.indexOf(d.headers["location"]) === -1 && redirectLoop.length < 5) { redirectLoop.push(d.headers["location"]); recursiveGet(d.headers["location"], cb, redirectLoop); return; } } cb(d); }); }; 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); if (!req.session || !req.session.slackToken) { if (req.urlObj.queryTokens.code) { Slack.getOauthToken(req.urlObj.queryTokens.code, config.rootUrl, (token) => { if (token) { req.session = sessionManager.lazyForRequest(req); req.session.setSlackToken(req.reqT, token); res.writeHeader("302", { Location: config.rootUrl ,"Set-Cookie": "sessID="+req.session.sessId }); sessionManager.saveSession(req.session); res.end(); } else { redirectToSlackAuth(res); } }); } else { redirectToSlackAuth(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.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(); res.chatContext.push(slackManager.lazyGet(req.session, req.reqT)); if (req.urlObj.match(["api", "hist"])) { if (!req.urlObj.queryTokens.room) { res.writeHeader("400", "Bad request"); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (ctx) { ctx.fetchHistory(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]); res.writeHeader("204", "No Content"); } else { res.writeHeader("404", "Channel not found"); } } sessionManager.saveSession(req.session); res.end(); } else if (req.urlObj.match(["api", "typing"])) { if (!req.urlObj.queryTokens.room) { res.writeHeader("400", "Bad request"); res.end(); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (!ctx) { res.writeHeader("404", "Chan not found"); } else { ctx.sendTyping(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]); res.writeHeader("204", "No Content"); } res.end(); } } else if (req.urlObj.match(["api", "cmd"])) { if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.cmd) { res.writeHeader("400", "Bad request"); res.end(); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]) ,cmd = ctx.getChatContext().commands.data['/' +req.urlObj.queryTokens.cmd[0]]; if (!ctx) { res.writeHeader("404", "Chan not found"); } else if (!cmd) { res.writeHeader("404", "No such command"); } else { var args = req.urlObj.queryTokens.args ? req.urlObj.queryTokens.args[0] : ""; if (args === true) args = ""; ctx.sendCommand(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], cmd, args); res.writeHeader("204", "No Content"); } res.end(); } } else if (req.urlObj.match(["api", "markread"])) { if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts) { res.writeHeader("400", "Bad request"); res.end(); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]) ,ts = parseFloat(req.urlObj.queryTokens.ts[0]); if (!ctx) { res.writeHeader("404", "Chan Not Found"); } else if (isNaN(ts)) { res.writeHeader("400", "Invalid date"); } else { ctx.markRead(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], ts); res.writeHeader("204", "No Content"); } res.end(); } sessionManager.saveSession(req.session); } else if (req.urlObj.match(["api", "avatar"])) { if (!req.urlObj.queryTokens.user) { res.writeHeader("400", "Bad request"); res.end(); } else { var user = res.chatContext.getUser(req.urlObj.queryTokens.user[0]); if (!user) { res.writeHeader("404", "User Not Found"); res.end(); } else { // FIXME size=l var url = user.getSmallIcon(); if (!config.isDebug) res.setHeader('Cache-Control', 'private, max-age=' +60 * 60); // 1 hour cache for avatars recursiveGet(url, (d) => { d.pipe(res, { end: true }); }); } } sessionManager.saveSession(req.session); } else if (req.urlObj.match(["api", "msg"])) { if (req.method === 'POST') { if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.text) { res.writeHeader("400", "Bad request"); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (ctx) { var attachments = null; if (req.urlObj.queryTokens.attachments) { try { attachments = JSON.parse(decodeURIComponent(req.urlObj.queryTokens.attachments[0])); } catch (e) {} } if (req.urlObj.queryTokens.me) ctx.sendMeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text); else ctx.sendMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text, attachments); res.writeHeader("204", "No Content"); } else { res.writeHeader("404", "Channel not found"); } } } else if (req.method === "DELETE") { if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts) { res.writeHeader("400", "Bad request"); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (ctx) { ctx.removeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0]); res.writeHeader("204", "No Content"); } else { res.writeHeader("404", "Channel not found"); } } } else if (req.method === "PUT") { if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts || !req.urlObj.queryTokens.text) { res.writeHeader("400", "Bad request"); } else { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (ctx) { ctx.editMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0], req.urlObj.queryTokens.text); res.writeHeader("204", "No Content"); } else { res.writeHeader("404", "Channel not found"); } } } else { res.writeHeader("400", "Bad request"); } sessionManager.saveSession(req.session); res.end(); } else if (req.urlObj.match(["api", "reaction"])) { var chanId = req.urlObj.queryTokens["room"] ? req.urlObj.queryTokens["room"][0] : undefined ,msgId = req.urlObj.queryTokens["msg"] ? req.urlObj.queryTokens["msg"][0] : undefined ,reaction = req.urlObj.queryTokens["reaction"] ? req.urlObj.queryTokens["reaction"][0] : undefined; if (chanId && msgId && reaction) { var ctx = res.chatContext.getChannelContext(chanId); if (!ctx) { res.writeHeader("404", "Channel Not Found"); } else if (req.method === 'POST') { res.writeHeader("204", "No Content"); ctx.addReaction(ctx.getChatContext().channels[chanId], msgId, reaction); } else if (req.method === 'DELETE') { res.writeHeader("204", "No Content"); ctx.removeReaction(ctx.getChatContext().channels[chanId], msgId, reaction); } else { res.writeHeader("405", "Method not allowed"); } } else { res.writeHeader("400", "Missing Parameter"); } sessionManager.saveSession(req.session); res.end(); } else if (req.urlObj.match(["api", "file"])) { sessionManager.saveSession(req.session); if (req.urlObj.queryTokens.room) { var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]); if (ctx) { var uploadRequest = ctx.openUploadFileStream(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.headers["content-type"], (errorMsg) => { if (!errorMsg) res.writeHeader("204", "No Content"); else res.writeHeader("500", errorMsg); res.end(); }); req.on('end', () => { uploadRequest.end(); }); req.pipe(uploadRequest); } else { res.writeHeader("404", "Channel Not Found"); res.end(); } } else { res.writeHeader("400", "Bad Request"); res.end(); } } else if (req.urlObj.match(["api", "attachmentAction"])) { sessionManager.saveSession(req.session); // FIXME refactor from here chatContext => MultichatManager // FIXME read payload, replace _POST['service_id'] and _POST['payload']['channel_id'] with remote_id res.chatContext.sendAction(req.headers["content-type"], req, (result) => { res.end(result); }); } else if (req.urlObj.match(["api"])) { res.chatContext.poll( (req.urlObj.queryTokens.v ? parseInt(req.urlObj.queryTokens.v[0], 10) : 0) || 0 , (newData) => { if (!res.ended) { try { res.writeHeader("200", { "Content-Type": "application/json" }); res.end(JSON.stringify(newData)); } catch (e) {} } sessionManager.saveSession(req.session); }); } else { console.log(JSON.stringify(req.session)); console.log(JSON.stringify(req.urlObj)); console.log(JSON.stringify(req.cookies)); res.writeHeader("404", "Not Found"); res.end(); } } }; module.exports.HttpServ = Server;