skyguard-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/dist/app.d.ts +123 -0
  4. package/dist/app.js +198 -0
  5. package/dist/container/container.d.ts +60 -0
  6. package/dist/container/container.js +71 -0
  7. package/dist/exceptions/baseException.d.ts +6 -0
  8. package/dist/exceptions/baseException.js +15 -0
  9. package/dist/exceptions/contentDispositionException.d.ts +4 -0
  10. package/dist/exceptions/contentDispositionException.js +11 -0
  11. package/dist/exceptions/contentParserException.d.ts +10 -0
  12. package/dist/exceptions/contentParserException.js +21 -0
  13. package/dist/exceptions/fileDownloadException.d.ts +4 -0
  14. package/dist/exceptions/fileDownloadException.js +11 -0
  15. package/dist/exceptions/fileExistsException.d.ts +4 -0
  16. package/dist/exceptions/fileExistsException.js +11 -0
  17. package/dist/exceptions/helperExceptions.d.ts +10 -0
  18. package/dist/exceptions/helperExceptions.js +25 -0
  19. package/dist/exceptions/httpNotFoundException.d.ts +7 -0
  20. package/dist/exceptions/httpNotFoundException.js +14 -0
  21. package/dist/exceptions/index.d.ts +9 -0
  22. package/dist/exceptions/index.js +25 -0
  23. package/dist/exceptions/invalidHttpStatusException.d.ts +4 -0
  24. package/dist/exceptions/invalidHttpStatusException.js +11 -0
  25. package/dist/exceptions/sessionException.d.ts +4 -0
  26. package/dist/exceptions/sessionException.js +11 -0
  27. package/dist/exceptions/validationException.d.ts +13 -0
  28. package/dist/exceptions/validationException.js +32 -0
  29. package/dist/helpers/app.d.ts +4 -0
  30. package/dist/helpers/app.js +12 -0
  31. package/dist/helpers/http.d.ts +59 -0
  32. package/dist/helpers/http.js +77 -0
  33. package/dist/helpers/index.d.ts +1 -0
  34. package/dist/helpers/index.js +9 -0
  35. package/dist/http/httpAdapter.d.ts +26 -0
  36. package/dist/http/httpAdapter.js +2 -0
  37. package/dist/http/httpMethods.d.ts +14 -0
  38. package/dist/http/httpMethods.js +18 -0
  39. package/dist/http/index.d.ts +6 -0
  40. package/dist/http/index.js +13 -0
  41. package/dist/http/logger.d.ts +8 -0
  42. package/dist/http/logger.js +36 -0
  43. package/dist/http/nodeNativeHttp.d.ts +41 -0
  44. package/dist/http/nodeNativeHttp.js +73 -0
  45. package/dist/http/request.d.ts +85 -0
  46. package/dist/http/request.js +127 -0
  47. package/dist/http/response.d.ts +118 -0
  48. package/dist/http/response.js +179 -0
  49. package/dist/http/statusCodes.d.ts +1 -0
  50. package/dist/http/statusCodes.js +38 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +10 -0
  53. package/dist/middlewares/cors.d.ts +103 -0
  54. package/dist/middlewares/cors.js +91 -0
  55. package/dist/middlewares/session.d.ts +26 -0
  56. package/dist/middlewares/session.js +88 -0
  57. package/dist/parsers/contentParser.d.ts +27 -0
  58. package/dist/parsers/contentParser.js +2 -0
  59. package/dist/parsers/contentParserManager.d.ts +50 -0
  60. package/dist/parsers/contentParserManager.js +91 -0
  61. package/dist/parsers/index.d.ts +8 -0
  62. package/dist/parsers/index.js +30 -0
  63. package/dist/parsers/jsonParser.d.ts +10 -0
  64. package/dist/parsers/jsonParser.js +24 -0
  65. package/dist/parsers/multipartParser.d.ts +35 -0
  66. package/dist/parsers/multipartParser.js +120 -0
  67. package/dist/parsers/parserInterface.d.ts +37 -0
  68. package/dist/parsers/parserInterface.js +2 -0
  69. package/dist/parsers/textParser.d.ts +11 -0
  70. package/dist/parsers/textParser.js +19 -0
  71. package/dist/parsers/urlEncodedParser.d.ts +10 -0
  72. package/dist/parsers/urlEncodedParser.js +19 -0
  73. package/dist/parsers/xmlParser.d.ts +47 -0
  74. package/dist/parsers/xmlParser.js +158 -0
  75. package/dist/routing/index.d.ts +3 -0
  76. package/dist/routing/index.js +9 -0
  77. package/dist/routing/layer.d.ts +85 -0
  78. package/dist/routing/layer.js +117 -0
  79. package/dist/routing/router.d.ts +143 -0
  80. package/dist/routing/router.js +210 -0
  81. package/dist/routing/routerGroup.d.ts +79 -0
  82. package/dist/routing/routerGroup.js +103 -0
  83. package/dist/server/nodeNativeServer.d.ts +29 -0
  84. package/dist/server/nodeNativeServer.js +42 -0
  85. package/dist/sessions/cookieOptions.d.ts +72 -0
  86. package/dist/sessions/cookieOptions.js +2 -0
  87. package/dist/sessions/index.d.ts +4 -0
  88. package/dist/sessions/index.js +7 -0
  89. package/dist/sessions/memorySessionStorage.d.ts +112 -0
  90. package/dist/sessions/memorySessionStorage.js +170 -0
  91. package/dist/sessions/session.d.ts +80 -0
  92. package/dist/sessions/session.js +101 -0
  93. package/dist/sessions/sessionStorage.d.ts +105 -0
  94. package/dist/sessions/sessionStorage.js +2 -0
  95. package/dist/static/contentDisposition.d.ts +71 -0
  96. package/dist/static/contentDisposition.js +159 -0
  97. package/dist/static/fileDownload.d.ts +44 -0
  98. package/dist/static/fileDownload.js +88 -0
  99. package/dist/static/fileStaticHandler.d.ts +61 -0
  100. package/dist/static/fileStaticHandler.js +110 -0
  101. package/dist/static/index.d.ts +4 -0
  102. package/dist/static/index.js +11 -0
  103. package/dist/static/mimeTypes.d.ts +1 -0
  104. package/dist/static/mimeTypes.js +40 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -0
  106. package/dist/types/index.d.ts +122 -0
  107. package/dist/types/index.js +2 -0
  108. package/dist/validators/index.d.ts +5 -0
  109. package/dist/validators/index.js +22 -0
  110. package/dist/validators/rules/booleanRule.d.ts +11 -0
  111. package/dist/validators/rules/booleanRule.js +22 -0
  112. package/dist/validators/rules/dateRule.d.ts +16 -0
  113. package/dist/validators/rules/dateRule.js +44 -0
  114. package/dist/validators/rules/emailRule.d.ts +12 -0
  115. package/dist/validators/rules/emailRule.js +24 -0
  116. package/dist/validators/rules/index.d.ts +6 -0
  117. package/dist/validators/rules/index.js +15 -0
  118. package/dist/validators/rules/numberRule.d.ts +17 -0
  119. package/dist/validators/rules/numberRule.js +30 -0
  120. package/dist/validators/rules/requiredRule.d.ts +11 -0
  121. package/dist/validators/rules/requiredRule.js +21 -0
  122. package/dist/validators/rules/stringRule.d.ts +18 -0
  123. package/dist/validators/rules/stringRule.js +32 -0
  124. package/dist/validators/types.d.ts +55 -0
  125. package/dist/validators/types.js +2 -0
  126. package/dist/validators/validationRule.d.ts +53 -0
  127. package/dist/validators/validationRule.js +37 -0
  128. package/dist/validators/validationSchema.d.ts +145 -0
  129. package/dist/validators/validationSchema.js +198 -0
  130. package/dist/validators/validator.d.ts +58 -0
  131. package/dist/validators/validator.js +91 -0
  132. package/dist/views/helpersTemplate.d.ts +104 -0
  133. package/dist/views/helpersTemplate.js +186 -0
  134. package/dist/views/index.d.ts +4 -0
  135. package/dist/views/index.js +9 -0
  136. package/dist/views/raptorEngine.d.ts +127 -0
  137. package/dist/views/raptorEngine.js +165 -0
  138. package/dist/views/templateEngine.d.ts +80 -0
  139. package/dist/views/templateEngine.js +204 -0
  140. package/dist/views/view.d.ts +55 -0
  141. package/dist/views/view.js +2 -0
  142. package/package.json +79 -0
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentDisposition = void 0;
4
+ const contentDispositionException_1 = require("../exceptions/contentDispositionException");
5
+ /**
6
+ * Generates safe `Content-Disposition` values for file downloads.
7
+ *
8
+ * Implements RFC 6266 and RFC 8187 for broad browser compatibility.
9
+ * Prevents header injection and handles non-ASCII filenames correctly.
10
+ *
11
+ * @example
12
+ * const cd = new ContentDisposition();
13
+ *
14
+ * cd.attachment("report.pdf");
15
+ * // => 'attachment; filename="report.pdf"'
16
+ *
17
+ * cd.attachment("reporte año 2024.pdf");
18
+ * // => 'attachment; filename="reporte ano 2024.pdf"; filename*=UTF-8\'\'reporte%20a%C3%B1o%202024.pdf'
19
+ *
20
+ * cd.inline("image.jpg");
21
+ * // => 'inline; filename="image.jpg"'
22
+ */
23
+ class ContentDisposition {
24
+ /**
25
+ * Builds a `Content-Disposition` value for file downloads.
26
+ *
27
+ * @param filename - File name
28
+ * @returns `Content-Disposition` value
29
+ */
30
+ attachment(filename) {
31
+ return this.create("attachment", filename);
32
+ }
33
+ /**
34
+ * Builds a `Content-Disposition` value for inline rendering.
35
+ *
36
+ * @param filename - File name
37
+ * @returns `Content-Disposition` value
38
+ */
39
+ inline(filename) {
40
+ return this.create("inline", filename);
41
+ }
42
+ create(type, filename) {
43
+ if (!filename || typeof filename !== "string") {
44
+ throw new contentDispositionException_1.ContentDispositionException();
45
+ }
46
+ const sanitized = this.sanitizeFilename(filename);
47
+ const needsEncoding = this.needsEncoding(sanitized);
48
+ let disposition = type;
49
+ if (needsEncoding) {
50
+ const fallback = this.createAsciiFallback(sanitized);
51
+ disposition += `; filename="${this.escapeQuotes(fallback)}"`;
52
+ const encoded = this.encodeRFC8187(sanitized);
53
+ disposition += `; filename*=UTF-8''${encoded}`;
54
+ }
55
+ else {
56
+ disposition += `; filename="${this.escapeQuotes(sanitized)}"`;
57
+ }
58
+ return disposition;
59
+ }
60
+ sanitizeFilename(filename) {
61
+ return filename
62
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, "")
63
+ .replace(/["\r\n]/g, "")
64
+ .replace(/[/\\]/g, "")
65
+ .replace(/\s+/g, " ")
66
+ .trim();
67
+ }
68
+ needsEncoding(filename) {
69
+ return /[^\x20-\x7E]/.test(filename);
70
+ }
71
+ createAsciiFallback(filename) {
72
+ const charMap = {
73
+ á: "a",
74
+ é: "e",
75
+ í: "i",
76
+ ó: "o",
77
+ ú: "u",
78
+ ñ: "n",
79
+ Á: "A",
80
+ É: "E",
81
+ Í: "I",
82
+ Ó: "O",
83
+ Ú: "U",
84
+ Ñ: "N",
85
+ ü: "u",
86
+ Ü: "U",
87
+ ç: "c",
88
+ Ç: "C",
89
+ ß: "ss",
90
+ æ: "ae",
91
+ œ: "oe",
92
+ " ": " ",
93
+ };
94
+ let result = filename;
95
+ for (const [char, replacement] of Object.entries(charMap)) {
96
+ result = result.replace(new RegExp(char, "g"), replacement);
97
+ }
98
+ result = result.replace(/[^\x20-\x7E]/g, "");
99
+ return result;
100
+ }
101
+ escapeQuotes(filename) {
102
+ return filename.replace(/"/g, '\\"');
103
+ }
104
+ encodeRFC8187(filename) {
105
+ const attrChar = /[a-zA-Z0-9!#$&+.^_`|~-]/;
106
+ let encoded = "";
107
+ for (let i = 0; i < filename.length; i++) {
108
+ const char = filename[i];
109
+ if (attrChar.test(char)) {
110
+ encoded += char;
111
+ }
112
+ else {
113
+ const bytes = Buffer.from(char, "utf-8");
114
+ for (const byte of bytes) {
115
+ encoded += "%" + byte.toString(16).toUpperCase().padStart(2, "0");
116
+ }
117
+ }
118
+ }
119
+ return encoded;
120
+ }
121
+ /**
122
+ * Parses an existing `Content-Disposition` header value.
123
+ *
124
+ * Supports both `filename=` and `filename*=` (RFC 8187).
125
+ *
126
+ * @param header - Raw `Content-Disposition` header value
127
+ * @returns Parsed disposition type and filename (if present)
128
+ *
129
+ * @example
130
+ * const cd = new ContentDisposition();
131
+ * cd.parse('attachment; filename="report.pdf"');
132
+ * // => { type: "attachment", filename: "report.pdf" }
133
+ */
134
+ parse(header) {
135
+ const parts = header.split(";").map((p) => p.trim());
136
+ const type = parts[0];
137
+ let filename = null;
138
+ for (let i = 1; i < parts.length; i++) {
139
+ const part = parts[i];
140
+ if (part.startsWith("filename*=")) {
141
+ const value = part.substring(10);
142
+ const match = value.match(/^UTF-8''(.+)$/i);
143
+ if (match) {
144
+ filename = decodeURIComponent(match[1]);
145
+ break;
146
+ }
147
+ }
148
+ if (part.startsWith("filename=") && !filename) {
149
+ let value = part.substring(9);
150
+ if (value.startsWith('"') && value.endsWith('"')) {
151
+ value = value.slice(1, -1);
152
+ }
153
+ filename = value.replace(/\\"/g, '"');
154
+ }
155
+ }
156
+ return { type, filename };
157
+ }
158
+ }
159
+ exports.ContentDisposition = ContentDisposition;
@@ -0,0 +1,44 @@
1
+ import { Response } from "../http/response";
2
+ /**
3
+ * Helper utility for serving file downloads.
4
+ *
5
+ * Builds a {@link Response} configured with safe headers
6
+ * (`Content-Disposition`, `Content-Type`, `Content-Length`)
7
+ * to send files to the client.
8
+ */
9
+ export declare class FileDownloadHelper {
10
+ private contentDisposition;
11
+ constructor();
12
+ /**
13
+ * Creates a file download response.
14
+ *
15
+ * Resolves the file path, validates that it is a file,
16
+ * reads its contents, and returns a {@link Response}
17
+ * ready to be sent to the client.
18
+ *
19
+ * @param filePath - Path to the file on disk
20
+ * @param filename - Optional custom filename for the download
21
+ * @param headers - Optional additional response headers
22
+ * @returns A {@link Response} configured for file download
23
+ *
24
+ * @throws {FileDownloadException}
25
+ * Thrown if the file does not exist, is not a file,
26
+ * or cannot be read.
27
+ *
28
+ * @example
29
+ * const helper = new FileDownloadHelper();
30
+ *
31
+ * app.get("/download", async () => {
32
+ * return helper.download("./files/report.pdf");
33
+ * });
34
+ *
35
+ * // Custom filename and headers
36
+ * return helper.download(
37
+ * "./files/data.csv",
38
+ * "export.csv",
39
+ * { "Cache-Control": "no-store" }
40
+ * );
41
+ */
42
+ download(filePath: string, filename?: string, headers?: Record<string, string>): Promise<Response>;
43
+ private getMimeType;
44
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileDownloadHelper = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const mimeTypes_1 = require("./mimeTypes");
7
+ const contentDisposition_1 = require("./contentDisposition");
8
+ const response_1 = require("../http/response");
9
+ const fileDownloadException_1 = require("../exceptions/fileDownloadException");
10
+ /**
11
+ * Helper utility for serving file downloads.
12
+ *
13
+ * Builds a {@link Response} configured with safe headers
14
+ * (`Content-Disposition`, `Content-Type`, `Content-Length`)
15
+ * to send files to the client.
16
+ */
17
+ class FileDownloadHelper {
18
+ contentDisposition;
19
+ constructor() {
20
+ this.contentDisposition = new contentDisposition_1.ContentDisposition();
21
+ }
22
+ /**
23
+ * Creates a file download response.
24
+ *
25
+ * Resolves the file path, validates that it is a file,
26
+ * reads its contents, and returns a {@link Response}
27
+ * ready to be sent to the client.
28
+ *
29
+ * @param filePath - Path to the file on disk
30
+ * @param filename - Optional custom filename for the download
31
+ * @param headers - Optional additional response headers
32
+ * @returns A {@link Response} configured for file download
33
+ *
34
+ * @throws {FileDownloadException}
35
+ * Thrown if the file does not exist, is not a file,
36
+ * or cannot be read.
37
+ *
38
+ * @example
39
+ * const helper = new FileDownloadHelper();
40
+ *
41
+ * app.get("/download", async () => {
42
+ * return helper.download("./files/report.pdf");
43
+ * });
44
+ *
45
+ * // Custom filename and headers
46
+ * return helper.download(
47
+ * "./files/data.csv",
48
+ * "export.csv",
49
+ * { "Cache-Control": "no-store" }
50
+ * );
51
+ */
52
+ async download(filePath, filename, headers) {
53
+ try {
54
+ const fullPath = (0, node_path_1.resolve)(filePath);
55
+ const stats = await (0, promises_1.stat)(fullPath);
56
+ if (!stats.isFile()) {
57
+ throw new fileDownloadException_1.FileDownloadException(`Path is not a file: ${fullPath}`);
58
+ }
59
+ const content = await (0, promises_1.readFile)(fullPath);
60
+ const downloadName = filename || (0, node_path_1.basename)(fullPath);
61
+ const downloadHeaders = {
62
+ "Content-Disposition": this.contentDisposition.attachment(downloadName),
63
+ "Content-Length": content.length.toString(),
64
+ "Content-Type": this.getMimeType(fullPath),
65
+ };
66
+ if (headers) {
67
+ for (const [key, value] of Object.entries(headers)) {
68
+ // Prevent overriding Content-Disposition
69
+ if (key.toLowerCase() !== "content-disposition") {
70
+ downloadHeaders[key] = value;
71
+ }
72
+ }
73
+ }
74
+ return new response_1.Response()
75
+ .setStatus(200)
76
+ .setContent(content)
77
+ .setHeaders(downloadHeaders);
78
+ }
79
+ catch {
80
+ throw new fileDownloadException_1.FileDownloadException("Failed to download file");
81
+ }
82
+ }
83
+ getMimeType(filePath) {
84
+ const ext = (0, node_path_1.extname)(filePath).toLowerCase();
85
+ return mimeTypes_1.mimeTypesObject[ext] || "application/octet-stream";
86
+ }
87
+ }
88
+ exports.FileDownloadHelper = FileDownloadHelper;
@@ -0,0 +1,61 @@
1
+ import { Response } from "../http/response";
2
+ /**
3
+ * Static file handler for a public directory.
4
+ *
5
+ * Resolves and serves files from a configured `publicPath` using a URL prefix
6
+ * derived from the directory name (e.g. `/public`).
7
+ *
8
+ * Includes basic path traversal protection and sets cache-related headers.
9
+ *
10
+ * @example
11
+ * const staticFiles = new StaticFileHandler("./public");
12
+ *
13
+ * // In your request pipeline:
14
+ * const response = await staticFiles.tryServeFile(request.getUrl);
15
+ * if (response) return response;
16
+ */
17
+ export declare class StaticFileHandler {
18
+ private publicPath;
19
+ private urlPrefix;
20
+ private mimeTypes;
21
+ constructor(publicPath: string);
22
+ /**
23
+ * Returns the configured URL prefix.
24
+ *
25
+ * @returns URL prefix (e.g. `"/public"`)
26
+ */
27
+ getUrlPrefix(): string;
28
+ /**
29
+ * Checks whether the request path matches the static prefix.
30
+ *
31
+ * @param requestPath - Requested path
32
+ * @returns `true` if the path starts with the prefix
33
+ */
34
+ matchesPrefix(requestPath: string): boolean;
35
+ /**
36
+ * Attempts to serve a static file.
37
+ *
38
+ * Returns `null` if:
39
+ * - the request path does not match the prefix
40
+ * - the resolved path escapes the public directory
41
+ * - the file does not exist or is not a file
42
+ * - an I/O error occurs
43
+ *
44
+ * @param requestPath - Requested path (e.g. `"/public/css/style.css"`)
45
+ * @returns A {@link Response} containing the file, or `null` if not found
46
+ *
47
+ * @example
48
+ * const response = await staticFiles.tryServeFile("/public/app.js");
49
+ * if (response) return response;
50
+ */
51
+ tryServeFile(requestPath: string): Promise<Response | null>;
52
+ /**
53
+ * Checks whether a request path looks like a static file request.
54
+ *
55
+ * This is a fast heuristic: it requires a file extension and a known mime type.
56
+ *
57
+ * @param requestPath - Requested path
58
+ * @returns `true` if it looks like a static file request
59
+ */
60
+ isStaticFileRequest(requestPath: string): boolean;
61
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StaticFileHandler = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const promises_1 = require("node:fs/promises");
6
+ const mimeTypes_1 = require("./mimeTypes");
7
+ const response_1 = require("../http/response");
8
+ /**
9
+ * Static file handler for a public directory.
10
+ *
11
+ * Resolves and serves files from a configured `publicPath` using a URL prefix
12
+ * derived from the directory name (e.g. `/public`).
13
+ *
14
+ * Includes basic path traversal protection and sets cache-related headers.
15
+ *
16
+ * @example
17
+ * const staticFiles = new StaticFileHandler("./public");
18
+ *
19
+ * // In your request pipeline:
20
+ * const response = await staticFiles.tryServeFile(request.getUrl);
21
+ * if (response) return response;
22
+ */
23
+ class StaticFileHandler {
24
+ publicPath = "";
25
+ urlPrefix = "";
26
+ mimeTypes;
27
+ constructor(publicPath) {
28
+ this.publicPath = (0, node_path_1.normalize)(publicPath);
29
+ this.urlPrefix = "/" + (0, node_path_1.basename)(this.publicPath);
30
+ this.mimeTypes = mimeTypes_1.mimeTypesObject;
31
+ }
32
+ /**
33
+ * Returns the configured URL prefix.
34
+ *
35
+ * @returns URL prefix (e.g. `"/public"`)
36
+ */
37
+ getUrlPrefix() {
38
+ return this.urlPrefix;
39
+ }
40
+ /**
41
+ * Checks whether the request path matches the static prefix.
42
+ *
43
+ * @param requestPath - Requested path
44
+ * @returns `true` if the path starts with the prefix
45
+ */
46
+ matchesPrefix(requestPath) {
47
+ return requestPath.startsWith(this.urlPrefix);
48
+ }
49
+ /**
50
+ * Attempts to serve a static file.
51
+ *
52
+ * Returns `null` if:
53
+ * - the request path does not match the prefix
54
+ * - the resolved path escapes the public directory
55
+ * - the file does not exist or is not a file
56
+ * - an I/O error occurs
57
+ *
58
+ * @param requestPath - Requested path (e.g. `"/public/css/style.css"`)
59
+ * @returns A {@link Response} containing the file, or `null` if not found
60
+ *
61
+ * @example
62
+ * const response = await staticFiles.tryServeFile("/public/app.js");
63
+ * if (response) return response;
64
+ */
65
+ async tryServeFile(requestPath) {
66
+ try {
67
+ if (!this.matchesPrefix(requestPath))
68
+ return null;
69
+ const relativePath = requestPath.slice(this.urlPrefix.length);
70
+ const filePath = (0, node_path_1.resolve)(this.publicPath, relativePath.replace(/^[/\\]+/, ""));
71
+ // Prevent path traversal (must stay within publicPath)
72
+ if (!filePath.startsWith(this.publicPath + node_path_1.sep))
73
+ return null;
74
+ const stats = await (0, promises_1.stat)(filePath);
75
+ if (!stats.isFile())
76
+ return null;
77
+ const content = await (0, promises_1.readFile)(filePath);
78
+ const ext = (0, node_path_1.extname)(filePath).toLowerCase();
79
+ const contentType = this.mimeTypes[ext] || this.mimeTypes["default"];
80
+ return new response_1.Response()
81
+ .setContent(content)
82
+ .setHeaders({
83
+ "content-type": contentType,
84
+ "content-length": content.length.toString(),
85
+ "cache-control": "public, max-age=31536000",
86
+ "last-modified": stats.mtime.toUTCString(),
87
+ ETag: `"${stats.size}-${stats.mtime.getTime()}"`,
88
+ })
89
+ .setStatus(200);
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Checks whether a request path looks like a static file request.
97
+ *
98
+ * This is a fast heuristic: it requires a file extension and a known mime type.
99
+ *
100
+ * @param requestPath - Requested path
101
+ * @returns `true` if it looks like a static file request
102
+ */
103
+ isStaticFileRequest(requestPath) {
104
+ if (!this.matchesPrefix(requestPath))
105
+ return false;
106
+ const ext = (0, node_path_1.extname)(requestPath);
107
+ return ext !== "" && ext in this.mimeTypes;
108
+ }
109
+ }
110
+ exports.StaticFileHandler = StaticFileHandler;
@@ -0,0 +1,4 @@
1
+ export { ContentDisposition } from "./contentDisposition";
2
+ export { FileDownloadHelper } from "./fileDownload";
3
+ export { StaticFileHandler } from "./fileStaticHandler";
4
+ export { mimeTypesObject } from "./mimeTypes";
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mimeTypesObject = exports.StaticFileHandler = exports.FileDownloadHelper = exports.ContentDisposition = void 0;
4
+ var contentDisposition_1 = require("./contentDisposition");
5
+ Object.defineProperty(exports, "ContentDisposition", { enumerable: true, get: function () { return contentDisposition_1.ContentDisposition; } });
6
+ var fileDownload_1 = require("./fileDownload");
7
+ Object.defineProperty(exports, "FileDownloadHelper", { enumerable: true, get: function () { return fileDownload_1.FileDownloadHelper; } });
8
+ var fileStaticHandler_1 = require("./fileStaticHandler");
9
+ Object.defineProperty(exports, "StaticFileHandler", { enumerable: true, get: function () { return fileStaticHandler_1.StaticFileHandler; } });
10
+ var mimeTypes_1 = require("./mimeTypes");
11
+ Object.defineProperty(exports, "mimeTypesObject", { enumerable: true, get: function () { return mimeTypes_1.mimeTypesObject; } });
@@ -0,0 +1 @@
1
+ export declare const mimeTypesObject: Record<string, string>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mimeTypesObject = void 0;
4
+ exports.mimeTypesObject = {
5
+ // Text
6
+ ".html": "text/html",
7
+ ".css": "text/css",
8
+ ".js": "text/javascript",
9
+ ".json": "application/json",
10
+ ".xml": "application/xml",
11
+ ".txt": "text/plain",
12
+ // Images
13
+ ".png": "image/png",
14
+ ".jpg": "image/jpeg",
15
+ ".jpeg": "image/jpeg",
16
+ ".gif": "image/gif",
17
+ ".svg": "image/svg+xml",
18
+ ".webp": "image/webp",
19
+ ".ico": "image/x-icon",
20
+ // Fonts
21
+ ".woff": "font/woff",
22
+ ".woff2": "font/woff2",
23
+ ".ttf": "font/ttf",
24
+ ".otf": "font/otf",
25
+ ".eot": "application/vnd.ms-fontobject",
26
+ // Documents
27
+ ".pdf": "application/pdf",
28
+ ".doc": "application/msword",
29
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
30
+ // Archives
31
+ ".zip": "application/zip",
32
+ ".rar": "application/x-rar-compressed",
33
+ // Audio/Video
34
+ ".mp3": "audio/mpeg",
35
+ ".mp4": "video/mp4",
36
+ ".webm": "video/webm",
37
+ ".ogg": "audio/ogg",
38
+ // Default
39
+ default: "application/octet-stream",
40
+ };