static-server.js 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. /*
  2. Minimal static file server for the Cine Studio E2E tests.
  3. Serves the ArozOS web root (src/web) so the app and its shared
  4. scripts (../script/*) load exactly as they do in production. The
  5. tests seed their own media as in-memory blobs and stub the ArozOS
  6. backend, so server-side AGI endpoints (/system/*, /media) are not
  7. needed here and simply 404 - which the app already handles.
  8. */
  9. "use strict";
  10. const http = require("http");
  11. const fs = require("fs");
  12. const path = require("path");
  13. const MIME = {
  14. ".html": "text/html; charset=utf-8",
  15. ".js": "text/javascript; charset=utf-8",
  16. ".css": "text/css; charset=utf-8",
  17. ".json": "application/json; charset=utf-8",
  18. ".png": "image/png",
  19. ".jpg": "image/jpeg",
  20. ".jpeg": "image/jpeg",
  21. ".gif": "image/gif",
  22. ".webp": "image/webp",
  23. ".svg": "image/svg+xml",
  24. ".ico": "image/x-icon",
  25. ".woff": "font/woff",
  26. ".woff2": "font/woff2",
  27. ".ttf": "font/ttf",
  28. ".map": "application/json; charset=utf-8"
  29. };
  30. // Create (but do not start) a server rooted at webRoot.
  31. function createServer(webRoot) {
  32. const root = path.resolve(webRoot);
  33. return http.createServer(function (req, res) {
  34. let urlPath;
  35. try {
  36. urlPath = decodeURIComponent(req.url.split("?")[0].split("#")[0]);
  37. } catch (e) {
  38. res.writeHead(400);
  39. res.end("bad request");
  40. return;
  41. }
  42. if (urlPath.endsWith("/")) { urlPath += "index.html"; }
  43. // Resolve within the web root, rejecting path traversal.
  44. const target = path.join(root, urlPath);
  45. if (target !== root && !target.startsWith(root + path.sep)) {
  46. res.writeHead(403);
  47. res.end("forbidden");
  48. return;
  49. }
  50. fs.stat(target, function (err, stat) {
  51. if (err || !stat.isFile()) {
  52. res.writeHead(404);
  53. res.end("not found");
  54. return;
  55. }
  56. res.writeHead(200, {
  57. "Content-Type": MIME[path.extname(target).toLowerCase()] || "application/octet-stream",
  58. "Cache-Control": "no-store"
  59. });
  60. fs.createReadStream(target).pipe(res);
  61. });
  62. });
  63. }
  64. // Start a server and resolve with { server, port, baseURL }.
  65. function start(webRoot, port) {
  66. return new Promise(function (resolve, reject) {
  67. const server = createServer(webRoot);
  68. server.on("error", reject);
  69. server.listen(port || 0, "127.0.0.1", function () {
  70. const actual = server.address().port;
  71. resolve({ server: server, port: actual, baseURL: "http://127.0.0.1:" + actual });
  72. });
  73. });
  74. }
  75. module.exports = { createServer, start };