Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

server.js 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. , polyfillLibrary = require('polyfill-library')
  13. , check_output_directory = require("./check_output_directory.js");
  14. var MIN_NODE_VERSION = 8.0;
  15. if (parseFloat(process.versions.node) < MIN_NODE_VERSION) {
  16. console.warn(
  17. "!!! You are using node " + process.version +
  18. ", wbo requires at least " + MIN_NODE_VERSION + " !!!");
  19. }
  20. check_output_directory(config.HISTORY_DIR);
  21. sockets.start(app);
  22. app.listen(config.PORT, config.HOST);
  23. log("server started", { port: config.PORT });
  24. var CSP = "default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:";
  25. var fileserver = serveStatic(config.WEBROOT, {
  26. maxAge: 2 * 3600 * 1000,
  27. setHeaders: function (res) {
  28. res.setHeader("X-UA-Compatible", "IE=Edge");
  29. res.setHeader("Content-Security-Policy", CSP);
  30. }
  31. });
  32. var errorPage = fs.readFileSync(path.join(config.WEBROOT, "error.html"));
  33. function serveError(request, response) {
  34. return function (err) {
  35. log("error", { "error": err && err.toString(), "url": request.url });
  36. response.writeHead(err ? 500 : 404, { "Content-Length": errorPage.length });
  37. response.end(errorPage);
  38. }
  39. }
  40. function logRequest(request) {
  41. log('connection', {
  42. ip: request.connection.remoteAddress,
  43. original_ip: request.headers['x-forwarded-for'] || request.headers['forwarded'],
  44. user_agent: request.headers['user-agent'],
  45. referer: request.headers['referer'],
  46. language: request.headers['accept-language'],
  47. url: request.url,
  48. });
  49. }
  50. function handler(request, response) {
  51. try {
  52. handleRequest(request, response);
  53. } catch (err) {
  54. console.trace(err);
  55. response.writeHead(500, { 'Content-Type': 'text/plain' });
  56. response.end(err.toString());
  57. }
  58. }
  59. const boardTemplate = new templating.BoardTemplate(path.join(config.WEBROOT, 'board.html'));
  60. const indexTemplate = new templating.Template(path.join(config.WEBROOT, 'index.html'));
  61. function validateBoardName(boardName) {
  62. if (/^[\w%\-_~()]*$/.test(boardName)) return boardName;
  63. throw new Error("Illegal board name: " + boardName);
  64. }
  65. function handleRequest(request, response) {
  66. var parsedUrl = url.parse(request.url, true);
  67. var parts = parsedUrl.pathname.split('/');
  68. if (parts[0] === '') parts.shift();
  69. switch (parts[0]) {
  70. case "boards":
  71. // "boards" refers to the root directory
  72. if (parts.length === 1) {
  73. // '/boards?board=...' This allows html forms to point to boards
  74. var boardName = parsedUrl.query.board || "anonymous";
  75. var headers = { Location: 'boards/' + encodeURIComponent(boardName) };
  76. response.writeHead(301, headers);
  77. response.end();
  78. } else if (parts.length === 2 && request.url.indexOf('.') === -1) {
  79. validateBoardName(parts[1]);
  80. // If there is no dot and no directory, parts[1] is the board name
  81. boardTemplate.serve(request, response);
  82. } else { // Else, it's a resource
  83. request.url = "/" + parts.slice(1).join('/');
  84. fileserver(request, response, serveError(request, response));
  85. }
  86. break;
  87. case "download":
  88. var boardName = validateBoardName(parts[1]),
  89. history_file = path.join(config.HISTORY_DIR, "board-" + boardName + ".json");
  90. if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) {
  91. history_file += '.' + parts[2] + '.bak';
  92. }
  93. log("download", { "file": history_file });
  94. fs.readFile(history_file, function (err, data) {
  95. if (err) return serveError(request, response)(err);
  96. response.writeHead(200, {
  97. "Content-Type": "application/json",
  98. "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"',
  99. "Content-Length": data.length,
  100. });
  101. response.end(data);
  102. });
  103. break;
  104. case "export":
  105. case "preview":
  106. var boardName = validateBoardName(parts[1]),
  107. history_file = path.join(config.HISTORY_DIR, "board-" + boardName + ".json");
  108. response.writeHead(200, {
  109. "Content-Type": "image/svg+xml",
  110. "Content-Security-Policy": CSP,
  111. "Cache-Control": "public, max-age=30",
  112. });
  113. var t = Date.now();
  114. createSVG.renderBoard(history_file, response).then(function () {
  115. log("preview", { "board": boardName, "time": Date.now() - t });
  116. response.end();
  117. }).catch(function (err) {
  118. log("error", { "error": err.toString() });
  119. response.end('<text>Sorry, an error occured</text>');
  120. });
  121. break;
  122. case "random":
  123. var name = crypto.randomBytes(32).toString('base64').replace(/[^\w]/g, '-');
  124. response.writeHead(307, { 'Location': 'boards/' + name });
  125. response.end(name);
  126. break;
  127. case "polyfill.js": // serve tailored polyfills
  128. case "polyfill.min.js":
  129. polyfillLibrary.getPolyfillString({
  130. uaString: request.headers['user-agent'],
  131. minify: request.url.endsWith(".min.js"),
  132. features: {
  133. 'default': { flags: ['gated'] },
  134. 'es5': { flags: ['gated'] },
  135. 'es6': { flags: ['gated'] },
  136. 'es7': { flags: ['gated'] },
  137. 'es2017': { flags: ['gated'] },
  138. 'es2018': { flags: ['gated'] },
  139. 'es2019': { flags: ['gated'] },
  140. 'performance.now': { flags: ['gated'] },
  141. }
  142. }).then(function (bundleString) {
  143. response.setHeader('Cache-Control', 'private, max-age=172800, stale-while-revalidate=1728000');
  144. response.setHeader('Vary', 'User-Agent');
  145. response.setHeader('Content-Type', 'application/javascript');
  146. response.end(bundleString);
  147. });
  148. break;
  149. case "": // Index page
  150. logRequest(request);
  151. indexTemplate.serve(request, response);
  152. break;
  153. default:
  154. fileserver(request, response, serveError(request, response));
  155. }
  156. }
  157. module.exports = app;