You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server.js 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. var app = require('http').createServer(handler)
  2. , sockets = require('./sockets.js')
  3. , log = require("./log.js").log
  4. , path = require('path')
  5. , url = require('url')
  6. , fs = require("fs")
  7. , nodestatic = require("node-static")
  8. , createSVG = require("./createSVG.js")
  9. , handlebars = require("handlebars");
  10. var io = sockets.start(app);
  11. /**
  12. * Folder from which static files will be served
  13. * @const
  14. * @type {string}
  15. */
  16. var WEBROOT = path.join(__dirname, "../client-data");
  17. /**
  18. * Port on which the application will listen
  19. * @const
  20. * @type {number}
  21. */
  22. var PORT = parseInt(process.env['PORT']) || 8080;
  23. app.listen(PORT);
  24. log("server started", { port: PORT });
  25. var CSP = "default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:";
  26. var fileserver = new nodestatic.Server(WEBROOT, {
  27. "headers": {
  28. "X-UA-Compatible": "IE=Edge",
  29. "Content-Security-Policy": CSP,
  30. }
  31. });
  32. function serveError(request, response, err) {
  33. console.warn("Error serving '" + request.url + "' : " + err.status + " " + err.message);
  34. fileserver.serveFile('error.html', err.status, {}, request, response);
  35. }
  36. function logRequest(request) {
  37. log('connection', {
  38. ip: request.connection.remoteAddress,
  39. original_ip: request.headers['x-forwarded-for'] || request.headers['forwarded'],
  40. user_agent: request.headers['user-agent'],
  41. referer: request.headers['referer'],
  42. language: request.headers['accept-language'],
  43. url: request.url,
  44. });
  45. }
  46. function handler(request, response) {
  47. try {
  48. handleRequest(request, response);
  49. } catch (err) {
  50. console.trace(err);
  51. response.writeHead(500, { 'Content-Type': 'text/plain' });
  52. response.end(err.toString());
  53. }
  54. }
  55. function baseUrl(req) {
  56. var proto = req.headers['X-Forwarded-Proto'] || (req.connection.encrypted ? 'https' : 'http');
  57. var host = req.headers['X-Forwarded-Host'] || req.headers.host;
  58. return proto + '://' + host;
  59. }
  60. var BOARD_HTML_TEMPLATE = handlebars.compile(
  61. fs.readFileSync(WEBROOT + '/board.html', { encoding: 'utf8' })
  62. );
  63. function handleRequest(request, response) {
  64. var parsedUrl = url.parse(request.url, true);
  65. var parts = parsedUrl.pathname.split('/');
  66. if (parts[0] === '') parts.shift();
  67. if (parts[0] === "boards") {
  68. // "boards" refers to the root directory
  69. if (parts.length === 1 && parsedUrl.query.board) {
  70. // '/boards?board=...' This allows html forms to point to boards
  71. var headers = { Location: 'boards/' + encodeURIComponent(parsedUrl.query.board) };
  72. response.writeHead(301, headers);
  73. response.end();
  74. } else if (parts.length === 2 && request.url.indexOf('.') === -1) {
  75. // If there is no dot and no directory, parts[1] is the board name
  76. logRequest(request);
  77. var board = decodeURIComponent(parts[1]);
  78. var body = BOARD_HTML_TEMPLATE({
  79. board: board,
  80. boardUriComponent: parts[1],
  81. baseUrl: baseUrl(request)
  82. });
  83. var headers = {
  84. 'Content-Length': Buffer.byteLength(body),
  85. 'Content-Type': 'text/html'
  86. };
  87. response.writeHead(200, headers);
  88. response.end(body);
  89. } else { // Else, it's a resource
  90. request.url = "/" + parts.slice(1).join('/');
  91. fileserver.serve(request, response, function (err, res) {
  92. if (err) serveError(request, response, err);
  93. });
  94. }
  95. } else if (parts[0] === "download") {
  96. var boardName = encodeURIComponent(parts[1]),
  97. history_file = "../server-data/board-" + boardName + ".json",
  98. headers = {
  99. "Content-Type": "application/json",
  100. "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"'
  101. };
  102. var promise = fileserver.serveFile(history_file, 200, headers, request, response);
  103. promise.on("error", function (err) {
  104. console.error("Error while downloading history", err);
  105. response.statusCode = 404;
  106. response.end("ERROR: Unable to serve history file\n");
  107. });
  108. } else if (parts[0] === "preview") {
  109. var boardName = encodeURIComponent(parts[1]),
  110. history_file = path.join(__dirname, "..", "server-data", "board-" + boardName + ".json");
  111. createSVG.renderBoard(history_file, function (err, svg) {
  112. if (err) {
  113. response.writeHead(404, { 'Content-Type': 'application/json' });
  114. response.end(JSON.stringify(err));
  115. }
  116. response.writeHead(200, {
  117. "Content-Type": "image/svg+xml",
  118. "Content-Security-Policy": CSP,
  119. 'Content-Length': Buffer.byteLength(svg),
  120. });
  121. response.end(svg);
  122. });
  123. } else {
  124. if (parts[0] === '') logRequest(request);
  125. fileserver.serve(request, response, function (err, res) {
  126. if (err) {
  127. logRequest(request);
  128. serveError(request, response, err);
  129. }
  130. });
  131. }
  132. }