index.cjs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
  9. var __export = (target, all) => {
  10. for (var name in all)
  11. __defProp(target, name, { get: all[name], enumerable: true });
  12. };
  13. var __copyProps = (to, from, except, desc) => {
  14. if (from && typeof from === "object" || typeof from === "function") {
  15. for (let key of __getOwnPropNames(from))
  16. if (!__hasOwnProp.call(to, key) && key !== except)
  17. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  18. }
  19. return to;
  20. };
  21. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  22. // If the importer is in node compatibility mode or this is not an ESM
  23. // file that has been converted to a CommonJS file using a Babel-
  24. // compatible transform (i.e. "__esModule" has not been set), then set
  25. // "default" to the CommonJS "module.exports" for node compatibility.
  26. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  27. mod
  28. ));
  29. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  30. // src/index.js
  31. var index_exports = {};
  32. __export(index_exports, {
  33. HttpStaticRouter: () => HttpStaticRouter
  34. });
  35. module.exports = __toCommonJS(index_exports);
  36. // src/http-static-router.js
  37. var import_path = __toESM(require("path"), 1);
  38. var import_mime_types = __toESM(require("mime-types"), 1);
  39. var import_fs = __toESM(require("fs"), 1);
  40. // src/utils/escape-regexp.js
  41. function escapeRegexp(input) {
  42. return String(input).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  43. }
  44. __name(escapeRegexp, "escapeRegexp");
  45. // src/utils/normalize-path.js
  46. function normalizePath(value, noStartingSlash = false) {
  47. if (typeof value !== "string") {
  48. return "/";
  49. }
  50. const res = value.trim().replace(/\/+/g, "/").replace(/(^\/|\/$)/g, "");
  51. return noStartingSlash ? res : "/" + res;
  52. }
  53. __name(normalizePath, "normalizePath");
  54. // src/http-static-router.js
  55. var import_js_service = require("@e22m4u/js-service");
  56. var import_js_format = require("@e22m4u/js-format");
  57. var _HttpStaticRouter = class _HttpStaticRouter extends import_js_service.DebuggableService {
  58. /**
  59. * Routes.
  60. *
  61. * @protected
  62. */
  63. _routes = [];
  64. /**
  65. * Constructor.
  66. *
  67. * @param {import('@e22m4u/js-service').ServiceContainer} container
  68. */
  69. constructor(container) {
  70. super(container, {
  71. noEnvironmentNamespace: true,
  72. namespace: "jsHttpStaticRouter"
  73. });
  74. }
  75. /**
  76. * Add route.
  77. *
  78. * @param {string} remotePath
  79. * @param {string} resourcePath
  80. * @returns {object}
  81. */
  82. addRoute(remotePath, resourcePath) {
  83. const debug = this.getDebuggerFor(this.addRoute);
  84. resourcePath = import_path.default.resolve(resourcePath);
  85. debug("Adding a new route.");
  86. debug("Resource path is %v.", resourcePath);
  87. debug("Remote path is %v.", remotePath);
  88. let stats;
  89. try {
  90. stats = import_fs.default.statSync(resourcePath);
  91. } catch (error) {
  92. console.error(error);
  93. throw new import_js_format.InvalidArgumentError(
  94. "Static resource path does not exist %v.",
  95. resourcePath
  96. );
  97. }
  98. const isFile = stats.isFile();
  99. debug("Resource type is %s.", isFile ? "File" : "Folder");
  100. const normalizedRemotePath = normalizePath(remotePath);
  101. const escapedRemotePath = escapeRegexp(normalizedRemotePath);
  102. const regexp = isFile ? new RegExp(`^${escapedRemotePath}$`) : new RegExp(`^${escapedRemotePath}(?:$|\\/)`);
  103. const route = { remotePath, resourcePath, regexp, isFile };
  104. this._routes.push(route);
  105. this._routes.sort((a, b) => b.remotePath.length - a.remotePath.length);
  106. return this;
  107. }
  108. /**
  109. * Match route.
  110. *
  111. * @param {import('http').IncomingMessage} req
  112. * @returns {object|undefined}
  113. */
  114. matchRoute(req) {
  115. const debug = this.getDebuggerFor(this.matchRoute);
  116. debug("Matching routes with incoming request.");
  117. const url = (req.url || "/").replace(/\?.*$/, "");
  118. debug("Incoming request is %s %v.", req.method, url);
  119. if (req.method !== "GET" && req.method !== "HEAD") {
  120. debug("Method not allowed.");
  121. return;
  122. }
  123. debug("Walking through %v routes.", this._routes.length);
  124. const route = this._routes.find((route2) => {
  125. const res = route2.regexp.test(url);
  126. const phrase = res ? "matched" : "not matched";
  127. debug("Resource %v %s.", route2.resourcePath, phrase);
  128. return res;
  129. });
  130. route ? debug("Resource %v matched.", route.resourcePath) : debug("No route matched.");
  131. return route;
  132. }
  133. /**
  134. * Send file by route.
  135. *
  136. * @param {object} route
  137. * @param {import('http').IncomingMessage} req
  138. * @param {import('http').ServerResponse} res
  139. */
  140. sendFileByRoute(route, req, res) {
  141. const reqUrl = req.url || "/";
  142. const reqPath = reqUrl.replace(/\?.*$/, "");
  143. let targetPath = route.resourcePath;
  144. if (!route.isFile) {
  145. const relativePath = reqPath.replace(route.regexp, "");
  146. targetPath = import_path.default.join(route.resourcePath, relativePath);
  147. }
  148. targetPath = import_path.default.resolve(targetPath);
  149. const resourceRoot = import_path.default.resolve(route.resourcePath);
  150. if (!targetPath.startsWith(resourceRoot)) {
  151. res.writeHead(403, { "content-type": "text/plain" });
  152. res.end("403 Forbidden");
  153. return;
  154. }
  155. import_fs.default.stat(targetPath, (statsError, stats) => {
  156. if (statsError) {
  157. return _handleFsError(statsError, res);
  158. }
  159. if (stats.isDirectory()) {
  160. if (/[^/]$/.test(reqPath)) {
  161. const searchMatch = reqUrl.match(/\?.*$/);
  162. const search = searchMatch ? searchMatch[0] : "";
  163. const normalizedPath = reqUrl.replace(/\/{2,}/g, "/");
  164. res.writeHead(302, { location: `${normalizedPath}/${search}` });
  165. res.end();
  166. return;
  167. }
  168. if (/\/{2,}/.test(reqUrl)) {
  169. const normalizedUrl = reqUrl.replace(/\/{2,}/g, "/");
  170. res.writeHead(302, { location: normalizedUrl });
  171. res.end();
  172. return;
  173. }
  174. targetPath = import_path.default.join(targetPath, "index.html");
  175. }
  176. const extname = import_path.default.extname(targetPath);
  177. const contentType = import_mime_types.default.contentType(extname) || "application/octet-stream";
  178. const fileStream = (0, import_fs.createReadStream)(targetPath);
  179. fileStream.on("error", (error) => {
  180. _handleFsError(error, res);
  181. });
  182. fileStream.on("open", () => {
  183. res.writeHead(200, { "content-type": contentType });
  184. if (req.method === "HEAD") {
  185. res.end();
  186. return;
  187. }
  188. fileStream.pipe(res);
  189. });
  190. });
  191. }
  192. };
  193. __name(_HttpStaticRouter, "HttpStaticRouter");
  194. var HttpStaticRouter = _HttpStaticRouter;
  195. function _handleFsError(error, res) {
  196. if (res.headersSent) {
  197. return;
  198. }
  199. if ("code" in error && error.code === "ENOENT") {
  200. res.writeHead(404, { "content-type": "text/plain" });
  201. res.write("404 Not Found");
  202. res.end();
  203. } else {
  204. res.writeHead(500, { "content-type": "text/plain" });
  205. res.write("500 Internal Server Error");
  206. res.end();
  207. }
  208. }
  209. __name(_handleFsError, "_handleFsError");
  210. // Annotate the CommonJS export names for ESM import in node:
  211. 0 && (module.exports = {
  212. HttpStaticRouter
  213. });