您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

server.js 5.4KB

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