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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. , crypto = require("crypto")
  8. , serveStatic = require("serve-static")
  9. , createSVG = require("./createSVG.js")
  10. , templating = require("./templating.js")
  11. , config = require("./configuration.js");
  12. var MIN_NODE_VERSION = 10.0;
  13. if (parseFloat(process.versions.node) < MIN_NODE_VERSION) {
  14. console.warn(
  15. "!!! You are using node " + process.version +
  16. ", wbo requires at least " + MIN_NODE_VERSION + " !!!");
  17. }
  18. var io = sockets.start(app);
  19. app.listen(config.PORT);
  20. log("server started", { port: config.PORT });
  21. var CSP = "default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:";
  22. var fileserver = serveStatic(config.WEBROOT, {
  23. maxAge: 2 * 3600 * 1000,
  24. setHeaders: function (res) {
  25. res.setHeader("X-UA-Compatible", "IE=Edge");
  26. res.setHeader("Content-Security-Policy", CSP);
  27. }
  28. });
  29. var errorPage = fs.readFileSync(path.join(config.WEBROOT, "error.html"));
  30. function serveError(request, response) {
  31. return function (err) {
  32. log("error", { "error": err, "url": request.url });
  33. response.writeHead(err ? 500 : 404, { "Content-Length": errorPage.length });
  34. response.end(errorPage);
  35. }
  36. }
  37. function logRequest(request) {
  38. log('connection', {
  39. ip: request.connection.remoteAddress,
  40. original_ip: request.headers['x-forwarded-for'] || request.headers['forwarded'],
  41. user_agent: request.headers['user-agent'],
  42. referer: request.headers['referer'],
  43. language: request.headers['accept-language'],
  44. url: request.url,
  45. });
  46. }
  47. function handler(request, response) {
  48. try {
  49. handleRequest(request, response);
  50. } catch (err) {
  51. console.trace(err);
  52. response.writeHead(500, { 'Content-Type': 'text/plain' });
  53. response.end(err.toString());
  54. }
  55. }
  56. const boardTemplate = new templating.BoardTemplate(path.join(config.WEBROOT, 'board.html'));
  57. const indexTemplate = new templating.Template(path.join(config.WEBROOT, 'index.html'));
  58. function handleRequest(request, response) {
  59. var parsedUrl = url.parse(request.url, true);
  60. var parts = parsedUrl.pathname.split('/');
  61. if (parts[0] === '') parts.shift();
  62. if (parts[0] === "boards") {
  63. // "boards" refers to the root directory
  64. if (parts.length === 1 && parsedUrl.query.board) {
  65. // '/boards?board=...' This allows html forms to point to boards
  66. var headers = { Location: 'boards/' + encodeURIComponent(parsedUrl.query.board) };
  67. response.writeHead(301, headers);
  68. response.end();
  69. } else if (parts.length === 2 && request.url.indexOf('.') === -1) {
  70. // If there is no dot and no directory, parts[1] is the board name
  71. boardTemplate.serve(request, response);
  72. } else { // Else, it's a resource
  73. request.url = "/" + parts.slice(1).join('/');
  74. fileserver(request, response, serveError(request, response));
  75. }
  76. } else if (parts[0] === "download") {
  77. var boardName = encodeURIComponent(parts[1]),
  78. history_file = path.join(config.HISTORY_DIR, "board-" + boardName + ".json");
  79. if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) {
  80. history_file += '.' + parts[2] + '.bak';
  81. }
  82. log("download", { "file": history_file });
  83. fs.readFile(history_file, function (err, data) {
  84. if (err) return serveError(request, response)(err);
  85. response.writeHead(200, {
  86. "Content-Type": "application/json",
  87. "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"',
  88. "Content-Length": data.length,
  89. });
  90. response.end(data);
  91. });
  92. } else if (parts[0] === "preview") {
  93. var boardName = encodeURIComponent(parts[1]),
  94. history_file = path.join(config.HISTORY_DIR, "board-" + boardName + ".json");
  95. createSVG.renderBoard(history_file, function (err, svg) {
  96. if (err) {
  97. log(err);
  98. response.writeHead(404, { 'Content-Type': 'application/json' });
  99. return response.end(JSON.stringify(err));
  100. }
  101. response.writeHead(200, {
  102. "Content-Type": "image/svg+xml",
  103. "Content-Security-Policy": CSP,
  104. 'Content-Length': Buffer.byteLength(svg),
  105. });
  106. response.end(svg);
  107. });
  108. } else if (parts[0] === "random") {
  109. var name = crypto.randomBytes(32).toString('base64').replace(/[^\w]/g, '-');
  110. response.writeHead(307, { 'Location': '/boards/' + name });
  111. response.end(name);
  112. } else if (parts[0] === "") { // Index page
  113. logRequest(request);
  114. indexTemplate.serve(request, response);
  115. } else {
  116. fileserver(request, response, serveError(request, response));
  117. }
  118. }
  119. module.exports = app;