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

server.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. , 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 = serveStatic(WEBROOT, {
  34. maxAge: 2 * 3600 * 1000,
  35. setHeaders: function (res) {
  36. res.setHeader("X-UA-Compatible", "IE=Edge");
  37. res.setHeader("Content-Security-Policy", CSP);
  38. }
  39. });
  40. var errorPage = fs.readFileSync(path.join(WEBROOT, "error.html"));
  41. function serveError(request, response) {
  42. return function (err) {
  43. log("error", { "error": err, "url": request.url });
  44. response.writeHead(err ? 500 : 404, { "Content-Length": errorPage.length });
  45. response.end(errorPage);
  46. }
  47. }
  48. function logRequest(request) {
  49. log('connection', {
  50. ip: request.connection.remoteAddress,
  51. original_ip: request.headers['x-forwarded-for'] || request.headers['forwarded'],
  52. user_agent: request.headers['user-agent'],
  53. referer: request.headers['referer'],
  54. language: request.headers['accept-language'],
  55. url: request.url,
  56. });
  57. }
  58. function handler(request, response) {
  59. try {
  60. handleRequest(request, response);
  61. } catch (err) {
  62. console.trace(err);
  63. response.writeHead(500, { 'Content-Type': 'text/plain' });
  64. response.end(err.toString());
  65. }
  66. }
  67. function baseUrl(req) {
  68. var proto = req.headers['X-Forwarded-Proto'] || (req.connection.encrypted ? 'https' : 'http');
  69. var host = req.headers['X-Forwarded-Host'] || req.headers.host;
  70. return proto + '://' + host;
  71. }
  72. var BOARD_HTML_TEMPLATE = handlebars.compile(
  73. fs.readFileSync(WEBROOT + '/board.html', { encoding: 'utf8' })
  74. );
  75. handlebars.registerHelper({
  76. translate: function (translations, str) {
  77. return translations[str] || str;
  78. },
  79. json: JSON.stringify.bind(JSON)
  80. });
  81. function handleRequest(request, response) {
  82. var parsedUrl = url.parse(request.url, true);
  83. var parts = parsedUrl.pathname.split('/');
  84. if (parts[0] === '') parts.shift();
  85. if (parts[0] === "boards") {
  86. // "boards" refers to the root directory
  87. if (parts.length === 1 && parsedUrl.query.board) {
  88. // '/boards?board=...' This allows html forms to point to boards
  89. var headers = { Location: 'boards/' + encodeURIComponent(parsedUrl.query.board) };
  90. response.writeHead(301, headers);
  91. response.end();
  92. } else if (parts.length === 2 && request.url.indexOf('.') === -1) {
  93. // If there is no dot and no directory, parts[1] is the board name
  94. logRequest(request);
  95. var lang = (
  96. parsedUrl.query.lang ||
  97. request.headers['accept-language'] ||
  98. ''
  99. ).slice(0, 2);
  100. var board = decodeURIComponent(parts[1]);
  101. var body = BOARD_HTML_TEMPLATE({
  102. board: board,
  103. boardUriComponent: parts[1],
  104. baseUrl: baseUrl(request),
  105. languages: Object.keys(TRANSLATIONS).concat("en"),
  106. language: lang in TRANSLATIONS ? lang : "en",
  107. translations: TRANSLATIONS[lang] || {}
  108. });
  109. var headers = {
  110. 'Content-Length': Buffer.byteLength(body),
  111. 'Content-Type': 'text/html',
  112. 'Vary': 'Accept-Language',
  113. 'Cache-Control': 'public, max-age=3600',
  114. };
  115. response.writeHead(200, headers);
  116. response.end(body);
  117. } else { // Else, it's a resource
  118. request.url = "/" + parts.slice(1).join('/');
  119. fileserver(request, response, serveError(request, response));
  120. }
  121. } else if (parts[0] === "download") {
  122. var boardName = encodeURIComponent(parts[1]),
  123. history_file = "server-data/board-" + boardName + ".json";
  124. if (parts.length > 2 && !isNaN(Date.parse(parts[2]))) {
  125. history_file += '.' + parts[2] + '.bak';
  126. }
  127. log("download", { "file": history_file });
  128. fs.readFile(history_file, function (err, data) {
  129. if (err) return serveError(request, response)(err);
  130. response.writeHead(200, {
  131. "Content-Type": "application/json",
  132. "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"',
  133. "Content-Length": data.length,
  134. });
  135. response.end(data);
  136. });
  137. } else if (parts[0] === "preview") {
  138. var boardName = encodeURIComponent(parts[1]),
  139. history_file = path.join(__dirname, "..", "server-data", "board-" + boardName + ".json");
  140. createSVG.renderBoard(history_file, function (err, svg) {
  141. if (err) {
  142. log(err);
  143. response.writeHead(404, { 'Content-Type': 'application/json' });
  144. return response.end(JSON.stringify(err));
  145. }
  146. response.writeHead(200, {
  147. "Content-Type": "image/svg+xml",
  148. "Content-Security-Policy": CSP,
  149. 'Content-Length': Buffer.byteLength(svg),
  150. });
  151. response.end(svg);
  152. });
  153. } else if (parts[0] === "random") {
  154. var name = crypto.randomBytes(32).toString('base64').replace(/[^\w]/g, '-');
  155. response.writeHead(307, { 'Location': '/boards/' + name });
  156. response.end(name);
  157. } else {
  158. if (parts[0] === '') logRequest(request);
  159. fileserver(request, response, serveError(request, response));
  160. }
  161. }