@designofadecade/server 4.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.
- package/CHANGELOG.md +62 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/client/ApiClient.d.ts +121 -0
- package/dist/client/ApiClient.d.ts.map +1 -0
- package/dist/client/ApiClient.js +289 -0
- package/dist/client/ApiClient.js.map +1 -0
- package/dist/context/Context.d.ts +71 -0
- package/dist/context/Context.d.ts.map +1 -0
- package/dist/context/Context.js +81 -0
- package/dist/context/Context.js.map +1 -0
- package/dist/docs/OpenApiGenerator.d.ts +135 -0
- package/dist/docs/OpenApiGenerator.d.ts.map +1 -0
- package/dist/docs/OpenApiGenerator.js +165 -0
- package/dist/docs/OpenApiGenerator.js.map +1 -0
- package/dist/events/Events.d.ts +52 -0
- package/dist/events/Events.d.ts.map +1 -0
- package/dist/events/Events.js +70 -0
- package/dist/events/Events.js.map +1 -0
- package/dist/events/EventsManager.d.ts +46 -0
- package/dist/events/EventsManager.d.ts.map +1 -0
- package/dist/events/EventsManager.js +137 -0
- package/dist/events/EventsManager.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/local/Local.d.ts +83 -0
- package/dist/local/Local.d.ts.map +1 -0
- package/dist/local/Local.js +114 -0
- package/dist/local/Local.js.map +1 -0
- package/dist/logger/Logger.d.ts +365 -0
- package/dist/logger/Logger.d.ts.map +1 -0
- package/dist/logger/Logger.js +582 -0
- package/dist/logger/Logger.js.map +1 -0
- package/dist/middleware/RequestLogger.d.ts +62 -0
- package/dist/middleware/RequestLogger.d.ts.map +1 -0
- package/dist/middleware/RequestLogger.js +71 -0
- package/dist/middleware/RequestLogger.js.map +1 -0
- package/dist/notifications/Slack.d.ts +19 -0
- package/dist/notifications/Slack.d.ts.map +1 -0
- package/dist/notifications/Slack.js +55 -0
- package/dist/notifications/Slack.js.map +1 -0
- package/dist/router/RouteError.d.ts +21 -0
- package/dist/router/RouteError.d.ts.map +1 -0
- package/dist/router/RouteError.js +31 -0
- package/dist/router/RouteError.js.map +1 -0
- package/dist/router/Router.d.ts +66 -0
- package/dist/router/Router.d.ts.map +1 -0
- package/dist/router/Router.js +327 -0
- package/dist/router/Router.js.map +1 -0
- package/dist/router/Routes.d.ts +30 -0
- package/dist/router/Routes.d.ts.map +1 -0
- package/dist/router/Routes.js +52 -0
- package/dist/router/Routes.js.map +1 -0
- package/dist/router/StaticFileHandler.d.ts +44 -0
- package/dist/router/StaticFileHandler.d.ts.map +1 -0
- package/dist/router/StaticFileHandler.js +148 -0
- package/dist/router/StaticFileHandler.js.map +1 -0
- package/dist/sanitizer/HtmlSanitizer.d.ts +306 -0
- package/dist/sanitizer/HtmlSanitizer.d.ts.map +1 -0
- package/dist/sanitizer/HtmlSanitizer.js +808 -0
- package/dist/sanitizer/HtmlSanitizer.js.map +1 -0
- package/dist/server/Server.d.ts +28 -0
- package/dist/server/Server.d.ts.map +1 -0
- package/dist/server/Server.js +95 -0
- package/dist/server/Server.js.map +1 -0
- package/dist/state/AppState.d.ts +64 -0
- package/dist/state/AppState.d.ts.map +1 -0
- package/dist/state/AppState.js +89 -0
- package/dist/state/AppState.js.map +1 -0
- package/dist/utils/HtmlRenderer.d.ts +6 -0
- package/dist/utils/HtmlRenderer.d.ts.map +1 -0
- package/dist/utils/HtmlRenderer.js +128 -0
- package/dist/utils/HtmlRenderer.js.map +1 -0
- package/dist/websocket/WebSocketMessageFormatter.d.ts +40 -0
- package/dist/websocket/WebSocketMessageFormatter.d.ts.map +1 -0
- package/dist/websocket/WebSocketMessageFormatter.js +99 -0
- package/dist/websocket/WebSocketMessageFormatter.js.map +1 -0
- package/dist/websocket/WebSocketServer.d.ts +14 -0
- package/dist/websocket/WebSocketServer.d.ts.map +1 -0
- package/dist/websocket/WebSocketServer.js +138 -0
- package/dist/websocket/WebSocketServer.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type Router from './Router.js';
|
|
2
|
+
import type { RouterRequest, RouterResponse, RouterMiddleware } from './Router.js';
|
|
3
|
+
import type Context from '../context/Context.js';
|
|
4
|
+
interface RouteRegistration {
|
|
5
|
+
path: string;
|
|
6
|
+
methods: string[];
|
|
7
|
+
pattern: URLPattern;
|
|
8
|
+
handler: (request: RouterRequest) => Promise<RouterResponse>;
|
|
9
|
+
middleware?: RouterMiddleware[];
|
|
10
|
+
}
|
|
11
|
+
export default class Routes {
|
|
12
|
+
#private;
|
|
13
|
+
/**
|
|
14
|
+
* Base path prefix for all routes in this class
|
|
15
|
+
* @type {string}
|
|
16
|
+
*/
|
|
17
|
+
static basePath: string;
|
|
18
|
+
/**
|
|
19
|
+
* Array of nested route classes to register
|
|
20
|
+
* @type {Array}
|
|
21
|
+
*/
|
|
22
|
+
static register: (new (router: Router, context?: Context) => Routes)[];
|
|
23
|
+
protected router: Router;
|
|
24
|
+
protected context?: Context;
|
|
25
|
+
constructor(router: Router, context?: Context);
|
|
26
|
+
get routerRoutes(): RouteRegistration[];
|
|
27
|
+
addRoute(path: string, methods: string | string[], handler: (request: RouterRequest) => Promise<RouterResponse>, middleware?: RouterMiddleware[]): void;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=Routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Routes.d.ts","sourceRoot":"","sources":["../../src/router/Routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,KAAK,OAAO,MAAM,uBAAuB,CAAC;AAEjD,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7D,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IACzB;;;OAGG;IACH,MAAM,CAAC,QAAQ,SAAM;IAErB;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC,EAAE,CAAM;IAG5E,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEhB,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;IAY7C,IAAI,YAAY,IAAI,iBAAiB,EAAE,CAEtC;IAED,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,OAAO,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,EAC5D,UAAU,CAAC,EAAE,gBAAgB,EAAE,GAC9B,IAAI;CAiCR"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export default class Routes {
|
|
2
|
+
/**
|
|
3
|
+
* Base path prefix for all routes in this class
|
|
4
|
+
* @type {string}
|
|
5
|
+
*/
|
|
6
|
+
static basePath = '';
|
|
7
|
+
/**
|
|
8
|
+
* Array of nested route classes to register
|
|
9
|
+
* @type {Array}
|
|
10
|
+
*/
|
|
11
|
+
static register = [];
|
|
12
|
+
#routerRoutes = [];
|
|
13
|
+
router;
|
|
14
|
+
context;
|
|
15
|
+
constructor(router, context) {
|
|
16
|
+
this.router = router;
|
|
17
|
+
this.context = context;
|
|
18
|
+
this.constructor.register.forEach((RouteClass) => {
|
|
19
|
+
const route = new RouteClass(router, context);
|
|
20
|
+
this.#routerRoutes.push(...route.routerRoutes);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
get routerRoutes() {
|
|
24
|
+
return this.#routerRoutes;
|
|
25
|
+
}
|
|
26
|
+
addRoute(path, methods, handler, middleware) {
|
|
27
|
+
// Validate inputs
|
|
28
|
+
if (typeof path !== 'string') {
|
|
29
|
+
throw new Error('Path must be a string');
|
|
30
|
+
}
|
|
31
|
+
if (typeof handler !== 'function') {
|
|
32
|
+
throw new Error('Handler must be a function');
|
|
33
|
+
}
|
|
34
|
+
const normalizedPath = `${this.constructor.basePath}${path}`.replace(/\/+/g, '/');
|
|
35
|
+
// Normalize methods to array
|
|
36
|
+
const methodsArray = Array.isArray(methods) ? methods : [methods];
|
|
37
|
+
// Validate HTTP methods
|
|
38
|
+
const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
39
|
+
const invalidMethods = methodsArray.filter((m) => !validMethods.includes(m));
|
|
40
|
+
if (invalidMethods.length > 0) {
|
|
41
|
+
throw new Error(`Invalid HTTP methods: ${invalidMethods.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
this.#routerRoutes.push({
|
|
44
|
+
path: normalizedPath,
|
|
45
|
+
methods: methodsArray,
|
|
46
|
+
pattern: new URLPattern({ pathname: normalizedPath }),
|
|
47
|
+
handler,
|
|
48
|
+
...(middleware && { middleware }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=Routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Routes.js","sourceRoot":"","sources":["../../src/router/Routes.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB;;;OAGG;IACH,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,QAAQ,GAA0D,EAAE,CAAC;IAE5E,aAAa,GAAwB,EAAE,CAAC;IAC9B,MAAM,CAAS;IACf,OAAO,CAAW;IAE5B,YAAY,MAAc,EAAE,OAAiB;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEtB,IAAI,CAAC,WAA6B,CAAC,QAAQ,CAAC,OAAO,CAClD,CAAC,UAA6D,EAAE,EAAE;YAChE,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,CAAC,CACF,CAAC;IACJ,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,QAAQ,CACN,IAAY,EACZ,OAA0B,EAC1B,OAA4D,EAC5D,UAA+B;QAE/B,kBAAkB;QAClB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,cAAc,GAAG,GAAI,IAAI,CAAC,WAA6B,CAAC,QAAQ,GAAG,IAAI,EAAE,CAAC,OAAO,CACrF,MAAM,EACN,GAAG,CACJ,CAAC;QAEF,6BAA6B;QAC7B,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAElE,wBAAwB;QACxB,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAClF,MAAM,cAAc,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;YACtB,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,YAAY;YACrB,OAAO,EAAE,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;YACrD,OAAO;YACP,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;SAClC,CAAC,CAAC;IACL,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface ServeResponse {
|
|
2
|
+
status: number;
|
|
3
|
+
headers: Record<string, string>;
|
|
4
|
+
body: string | Buffer;
|
|
5
|
+
}
|
|
6
|
+
interface StaticFileHandlerOptions {
|
|
7
|
+
cacheControl?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* StaticFileHandler - Serves static files with security and proper MIME types
|
|
11
|
+
*
|
|
12
|
+
* Handles serving static files from a base directory with directory traversal
|
|
13
|
+
* protection, proper content types, and caching headers.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const handler = new StaticFileHandler('./public');
|
|
17
|
+
* const response = await handler.serve('/assets/style.css');
|
|
18
|
+
*/
|
|
19
|
+
export default class StaticFileHandler {
|
|
20
|
+
#private;
|
|
21
|
+
static MIME_TYPES: Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new StaticFileHandler
|
|
24
|
+
*
|
|
25
|
+
* @param {string} baseDir - Base directory to serve files from (absolute path)
|
|
26
|
+
* @param {Object} options - Configuration options
|
|
27
|
+
* @param {string} [options.cacheControl='public, max-age=3600'] - Cache-Control header value
|
|
28
|
+
* @throws {Error} If baseDir is not a non-empty string
|
|
29
|
+
*/
|
|
30
|
+
constructor(baseDir: string, options?: StaticFileHandlerOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Serve a static file
|
|
33
|
+
*
|
|
34
|
+
* @param {string} requestPath - The requested file path
|
|
35
|
+
* @returns {Promise<Object>} Response object with status, headers, and body
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const response = await handler.serve('/assets/style.css');
|
|
39
|
+
* // Returns: { status: 200, headers: {...}, body: Buffer }
|
|
40
|
+
*/
|
|
41
|
+
serve(requestPath: string): Promise<ServeResponse>;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=StaticFileHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StaticFileHandler.d.ts","sourceRoot":"","sources":["../../src/router/StaticFileHandler.ts"],"names":[],"mappings":"AAIA,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,UAAU,wBAAwB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAiB;;IACpC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0BvC;IAKF;;;;;;;OAOG;gBACS,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B;IAQnE;;;;;;;;;OASG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;CAsFzD"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger } from '../logger/Logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* StaticFileHandler - Serves static files with security and proper MIME types
|
|
6
|
+
*
|
|
7
|
+
* Handles serving static files from a base directory with directory traversal
|
|
8
|
+
* protection, proper content types, and caching headers.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const handler = new StaticFileHandler('./public');
|
|
12
|
+
* const response = await handler.serve('/assets/style.css');
|
|
13
|
+
*/
|
|
14
|
+
export default class StaticFileHandler {
|
|
15
|
+
static MIME_TYPES = {
|
|
16
|
+
'.html': 'text/html',
|
|
17
|
+
'.css': 'text/css',
|
|
18
|
+
'.js': 'text/javascript',
|
|
19
|
+
'.json': 'application/json',
|
|
20
|
+
'.png': 'image/png',
|
|
21
|
+
'.jpg': 'image/jpeg',
|
|
22
|
+
'.jpeg': 'image/jpeg',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.ico': 'image/x-icon',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
'.ttf': 'font/ttf',
|
|
29
|
+
'.otf': 'font/otf',
|
|
30
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
31
|
+
'.webp': 'image/webp',
|
|
32
|
+
'.pdf': 'application/pdf',
|
|
33
|
+
'.xml': 'application/xml',
|
|
34
|
+
'.txt': 'text/plain',
|
|
35
|
+
'.wasm': 'application/wasm',
|
|
36
|
+
'.mp4': 'video/mp4',
|
|
37
|
+
'.webm': 'video/webm',
|
|
38
|
+
'.mp3': 'audio/mpeg',
|
|
39
|
+
'.wav': 'audio/wav',
|
|
40
|
+
'.zip': 'application/zip',
|
|
41
|
+
};
|
|
42
|
+
#baseDir;
|
|
43
|
+
#cacheControl;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new StaticFileHandler
|
|
46
|
+
*
|
|
47
|
+
* @param {string} baseDir - Base directory to serve files from (absolute path)
|
|
48
|
+
* @param {Object} options - Configuration options
|
|
49
|
+
* @param {string} [options.cacheControl='public, max-age=3600'] - Cache-Control header value
|
|
50
|
+
* @throws {Error} If baseDir is not a non-empty string
|
|
51
|
+
*/
|
|
52
|
+
constructor(baseDir, options = {}) {
|
|
53
|
+
if (!baseDir || typeof baseDir !== 'string') {
|
|
54
|
+
throw new Error('baseDir must be a non-empty string');
|
|
55
|
+
}
|
|
56
|
+
this.#baseDir = path.resolve(baseDir);
|
|
57
|
+
this.#cacheControl = options.cacheControl || 'public, max-age=3600';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Serve a static file
|
|
61
|
+
*
|
|
62
|
+
* @param {string} requestPath - The requested file path
|
|
63
|
+
* @returns {Promise<Object>} Response object with status, headers, and body
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const response = await handler.serve('/assets/style.css');
|
|
67
|
+
* // Returns: { status: 200, headers: {...}, body: Buffer }
|
|
68
|
+
*/
|
|
69
|
+
async serve(requestPath) {
|
|
70
|
+
try {
|
|
71
|
+
// Security: prevent directory traversal
|
|
72
|
+
// Resolve the full path and ensure it's within the base directory
|
|
73
|
+
const filePath = path.resolve(this.#baseDir, '.' + requestPath);
|
|
74
|
+
// Ensure file is within base directory (using resolve to handle .. properly)
|
|
75
|
+
const resolvedBase = path.resolve(this.#baseDir);
|
|
76
|
+
if (!filePath.startsWith(resolvedBase + path.sep) && filePath !== resolvedBase)
|
|
77
|
+
return {
|
|
78
|
+
status: 403,
|
|
79
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
80
|
+
body: 'Forbidden',
|
|
81
|
+
};
|
|
82
|
+
let stats = await fs.stat(filePath);
|
|
83
|
+
// Support index.html for directories
|
|
84
|
+
let finalPath = filePath;
|
|
85
|
+
if (stats.isDirectory()) {
|
|
86
|
+
finalPath = path.join(filePath, 'index.html');
|
|
87
|
+
try {
|
|
88
|
+
stats = await fs.stat(finalPath);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return {
|
|
92
|
+
status: 404,
|
|
93
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
94
|
+
body: 'Not Found',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!stats.isFile())
|
|
99
|
+
return {
|
|
100
|
+
status: 404,
|
|
101
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
102
|
+
body: 'Not Found',
|
|
103
|
+
};
|
|
104
|
+
const content = await fs.readFile(finalPath);
|
|
105
|
+
const ext = path.extname(finalPath).toLowerCase();
|
|
106
|
+
const contentType = StaticFileHandler.MIME_TYPES[ext] || 'application/octet-stream';
|
|
107
|
+
// Add charset for text-based content
|
|
108
|
+
const fullContentType = contentType.startsWith('text/') ||
|
|
109
|
+
contentType.includes('javascript') ||
|
|
110
|
+
contentType.includes('json')
|
|
111
|
+
? `${contentType}; charset=utf-8`
|
|
112
|
+
: contentType;
|
|
113
|
+
return {
|
|
114
|
+
status: 200,
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': fullContentType,
|
|
117
|
+
'Cache-Control': this.#cacheControl,
|
|
118
|
+
'Content-Length': stats.size.toString(),
|
|
119
|
+
'Last-Modified': stats.mtime.toUTCString(),
|
|
120
|
+
'X-Content-Type-Options': 'nosniff',
|
|
121
|
+
},
|
|
122
|
+
body: content,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'ENOENT') {
|
|
127
|
+
return {
|
|
128
|
+
status: 404,
|
|
129
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
130
|
+
body: 'Not Found',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
logger.error('Static file error', {
|
|
134
|
+
code: 'STATIC_FILE_ERROR',
|
|
135
|
+
source: 'StaticFileHandler.serve',
|
|
136
|
+
path: requestPath,
|
|
137
|
+
error,
|
|
138
|
+
errorCode: error.code,
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
status: 500,
|
|
142
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
143
|
+
body: 'Internal Server Error',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=StaticFileHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StaticFileHandler.js","sourceRoot":"","sources":["../../src/router/StaticFileHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Sanitization Utility
|
|
3
|
+
*
|
|
4
|
+
* Production-grade HTML sanitization to prevent XSS attacks and ensure safe HTML rendering.
|
|
5
|
+
*
|
|
6
|
+
* Security Features:
|
|
7
|
+
* - XSS Prevention: Blocks dangerous protocols (javascript:, data:, vbscript:, etc.)
|
|
8
|
+
* - DoS Protection: Enforces maximum input size limits
|
|
9
|
+
* - Script/Style Removal: Completely removes script and style tags with content
|
|
10
|
+
* - HTML Comment Stripping: Removes comments that could hide malicious content
|
|
11
|
+
* - URL Validation: Validates and sanitizes URLs in anchor tags
|
|
12
|
+
* - Entity Decoding: Safely decodes HTML entities with range validation
|
|
13
|
+
* - Attribute Escaping: Prevents attribute injection attacks
|
|
14
|
+
*
|
|
15
|
+
* @module HtmlSanitizer
|
|
16
|
+
* @version 2.0.0
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Result type for sanitization methods
|
|
20
|
+
*/
|
|
21
|
+
export interface SanitizationResult {
|
|
22
|
+
readonly sanitized: string;
|
|
23
|
+
readonly warnings?: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* HTML Sanitization utility class with static methods for secure HTML processing
|
|
27
|
+
*
|
|
28
|
+
* @class HtmlSanitizer
|
|
29
|
+
*/
|
|
30
|
+
export default class HtmlSanitizer {
|
|
31
|
+
/**
|
|
32
|
+
* Cleans HTML by allowing only specified tags and removing all others.
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Strips disallowed tags while preserving text content
|
|
36
|
+
* - Validates and sanitizes URLs in anchor tags
|
|
37
|
+
* - Removes HTML comments, script, and style tags
|
|
38
|
+
* - Enforces DoS protection with size limits
|
|
39
|
+
* - Validates allowed tags is a valid array
|
|
40
|
+
*
|
|
41
|
+
* @param html - The HTML string to clean
|
|
42
|
+
* @param allowedTags - Array of allowed tag names (case-insensitive)
|
|
43
|
+
* @returns Sanitized HTML string with only allowed tags
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Allow only paragraph and bold tags
|
|
47
|
+
* HtmlSanitizer.clean('<p>Hello <b>World</b></p><script>alert("xss")</script>', ['p', 'b']);
|
|
48
|
+
* // Returns: '<p>Hello <b>World</b></p>'
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Sanitize anchor tags with URL validation
|
|
52
|
+
* HtmlSanitizer.clean('<a href="https://example.com">Safe</a><a href="javascript:alert(1)">Unsafe</a>', ['a']);
|
|
53
|
+
* // Returns: '<a href="https://example.com">Safe</a><a>Unsafe</a>'
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // Allow common formatting tags
|
|
57
|
+
* HtmlSanitizer.clean(userInput, ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']);
|
|
58
|
+
*/
|
|
59
|
+
static clean(html: string, allowedTags: string[]): string;
|
|
60
|
+
/**
|
|
61
|
+
* Sanitizes basic HTML content by allowing only b, i, u, and a tags.
|
|
62
|
+
* Common use case for simple user-generated content.
|
|
63
|
+
*
|
|
64
|
+
* @param html - The HTML string to sanitize
|
|
65
|
+
* @returns Sanitized HTML string with basic formatting
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* HtmlSanitizer.sanitizeBasicHtml('<b>Bold</b> and <i>italic</i> text');
|
|
69
|
+
* // Returns: '<b>Bold</b> and <i>italic</i> text'
|
|
70
|
+
*/
|
|
71
|
+
static sanitizeBasicHtml(html: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Replaces or adds attributes to specific HTML tags.
|
|
74
|
+
* Useful for adding email-specific attributes like target="_blank" to links.
|
|
75
|
+
*
|
|
76
|
+
* @param html - The HTML string to process
|
|
77
|
+
* @param tagName - The tag name to target (e.g., 'a')
|
|
78
|
+
* @param attributes - Object with attribute key-value pairs to add/replace
|
|
79
|
+
* @returns HTML with updated tag attributes
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* HtmlSanitizer.replaceTagAttributes('<a href="/link">Click</a>', 'a', {
|
|
83
|
+
* target: '_blank',
|
|
84
|
+
* style: 'color: blue;'
|
|
85
|
+
* });
|
|
86
|
+
* // Returns: '<a href="/link" target="_blank" style="color: blue;">Click</a>'
|
|
87
|
+
*/
|
|
88
|
+
static replaceTagAttributes(html: string, tagName: string, attributes: Record<string, string>): string;
|
|
89
|
+
/**
|
|
90
|
+
* Strips all HTML tags from content, leaving only plain text.
|
|
91
|
+
* Removes comments, scripts, styles, and decodes HTML entities.
|
|
92
|
+
*
|
|
93
|
+
* @param html - The HTML string to strip
|
|
94
|
+
* @returns Plain text without any HTML tags
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* HtmlSanitizer.stripAllTags('<p>Hello <b>World</b></p>');
|
|
98
|
+
* // Returns: 'Hello World'
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* HtmlSanitizer.stripAllTags('<script>alert(1)</script>Safe text');
|
|
102
|
+
* // Returns: 'Safe text'
|
|
103
|
+
*/
|
|
104
|
+
static stripAllTags(html: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* Alias for stripAllTags() - strips all HTML tags from content.
|
|
107
|
+
* Provided for convenience and backwards compatibility.
|
|
108
|
+
*
|
|
109
|
+
* @param html - The HTML string to strip
|
|
110
|
+
* @returns Plain text without HTML tags
|
|
111
|
+
* @see stripAllTags
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* HtmlSanitizer.stripAll('<div>Content</div>');
|
|
115
|
+
* // Returns: 'Content'
|
|
116
|
+
*/
|
|
117
|
+
static stripAll(html: string): string;
|
|
118
|
+
/**
|
|
119
|
+
* Sanitizes text for safe use in HTML attributes.
|
|
120
|
+
* Escapes characters that could break out of attribute context or inject code.
|
|
121
|
+
*
|
|
122
|
+
* Escapes: &, ", ', <, >, newlines, carriage returns
|
|
123
|
+
*
|
|
124
|
+
* @param text - The text to sanitize
|
|
125
|
+
* @returns HTML-safe text for use in attributes
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* HtmlSanitizer.sanitizeForAttribute('value with "quotes"');
|
|
129
|
+
* // Returns: 'value with "quotes"'
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const value = HtmlSanitizer.sanitizeForAttribute(userInput);
|
|
133
|
+
* // Safe in: <div data-value="${value}">...</div>
|
|
134
|
+
*/
|
|
135
|
+
static sanitizeForAttribute(text: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Sanitizes text for safe display in HTML content.
|
|
138
|
+
* Escapes HTML special characters to prevent XSS attacks.
|
|
139
|
+
*
|
|
140
|
+
* Escapes: &, <, >, ", '
|
|
141
|
+
*
|
|
142
|
+
* @param text - The text to sanitize
|
|
143
|
+
* @returns HTML-safe text that can be inserted into HTML content
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* HtmlSanitizer.sanitizeForHtml('<script>alert("xss")</script>');
|
|
147
|
+
* // Returns: '<script>alert("xss")</script>'
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* const safeText = HtmlSanitizer.sanitizeForHtml(userInput);
|
|
151
|
+
* // Safe in: <p>${safeText}</p>
|
|
152
|
+
*/
|
|
153
|
+
static sanitizeForHtml(text: string): string;
|
|
154
|
+
/**
|
|
155
|
+
* Validates if a URL is safe for use in links.
|
|
156
|
+
* Blocks dangerous protocols and validates against common XSS vectors.
|
|
157
|
+
*
|
|
158
|
+
* Blocked protocols: javascript:, vbscript:, data:, file:, about:, blob:
|
|
159
|
+
* Allowed protocols: https://, http://, mailto:, tel:, sms:, relative paths
|
|
160
|
+
*
|
|
161
|
+
* @param url - The URL to validate
|
|
162
|
+
* @returns True if the URL is considered safe, false otherwise
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* HtmlSanitizer.isValidUrl('https://example.com');
|
|
166
|
+
* // Returns: true
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* HtmlSanitizer.isValidUrl('javascript:alert(1)');
|
|
170
|
+
* // Returns: false
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* HtmlSanitizer.isValidUrl('/relative/path');
|
|
174
|
+
* // Returns: true
|
|
175
|
+
*/
|
|
176
|
+
static isValidUrl(url: string): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Validates if an email address is properly formatted.
|
|
179
|
+
* Performs comprehensive email validation following RFC 5321/5322 standards.
|
|
180
|
+
*
|
|
181
|
+
* Validation Rules:
|
|
182
|
+
* - Maximum length: 254 characters (RFC 5321)
|
|
183
|
+
* - Local part (before @): 1-64 characters, allows a-z, 0-9, . _ + % -
|
|
184
|
+
* - Local part cannot start/end with dot or contain consecutive dots
|
|
185
|
+
* - Domain part (after @): 1-255 characters, requires valid TLD
|
|
186
|
+
* - Domain labels: 1-63 characters each, allows a-z, 0-9, hyphen (not at start/end)
|
|
187
|
+
* - Must contain exactly one @ symbol
|
|
188
|
+
* - Case-insensitive validation
|
|
189
|
+
*
|
|
190
|
+
* Security Features:
|
|
191
|
+
* - DoS protection with length limits
|
|
192
|
+
* - Rejects malformed patterns that could be used for injection
|
|
193
|
+
* - Validates against common email spoofing patterns
|
|
194
|
+
*
|
|
195
|
+
* Note: This performs format validation only. For production use, consider:
|
|
196
|
+
* - DNS MX record verification
|
|
197
|
+
* - Email verification (send confirmation email)
|
|
198
|
+
* - Third-party email validation services
|
|
199
|
+
* - Disposable email detection
|
|
200
|
+
*
|
|
201
|
+
* @param email - The email address to validate
|
|
202
|
+
* @returns True if email format is valid, false otherwise
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* HtmlSanitizer.isValidEmail('user@example.com');
|
|
206
|
+
* // Returns: true
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* HtmlSanitizer.isValidEmail('user+tag@subdomain.example.co.uk');
|
|
210
|
+
* // Returns: true
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* HtmlSanitizer.isValidEmail('invalid.email');
|
|
214
|
+
* // Returns: false
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* HtmlSanitizer.isValidEmail('user..name@example.com');
|
|
218
|
+
* // Returns: false (consecutive dots)
|
|
219
|
+
*/
|
|
220
|
+
static isValidEmail(email: string): boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Decodes common HTML entities to their character equivalents.
|
|
223
|
+
* Safely handles both named entities and numeric character references.
|
|
224
|
+
*
|
|
225
|
+
* Features:
|
|
226
|
+
* - Decodes common named entities (&, <, , etc.)
|
|
227
|
+
* - Decodes numeric entities (A and A both -> 'A')
|
|
228
|
+
* - Validates character codes are in valid Unicode range
|
|
229
|
+
* - Excludes control characters (except tab, newline, CR)
|
|
230
|
+
*
|
|
231
|
+
* @param text - Text containing HTML entities
|
|
232
|
+
* @returns Text with entities decoded to characters
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* HtmlSanitizer.decodeHtmlEntities('<p>Hello & goodbye</p>');
|
|
236
|
+
* // Returns: '<p>Hello & goodbye</p>'
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* HtmlSanitizer.decodeHtmlEntities('ABC'); // ABC
|
|
240
|
+
* HtmlSanitizer.decodeHtmlEntities('ABC'); // ABC
|
|
241
|
+
*/
|
|
242
|
+
static decodeHtmlEntities(text: string): string;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* USAGE GUIDELINES
|
|
246
|
+
*
|
|
247
|
+
* 1. USER-GENERATED CONTENT
|
|
248
|
+
* Always sanitize user input before storing or displaying:
|
|
249
|
+
* ```typescript
|
|
250
|
+
* // For rich text with limited formatting
|
|
251
|
+
* const sanitized = HtmlSanitizer.clean(userInput, ['p', 'br', 'strong', 'em', 'a']);
|
|
252
|
+
*
|
|
253
|
+
* // For plain text display
|
|
254
|
+
* const plainText = HtmlSanitizer.stripAll(userInput);
|
|
255
|
+
* ```
|
|
256
|
+
*
|
|
257
|
+
* 2. ATTRIBUTE VALUES
|
|
258
|
+
* Always escape text used in HTML attributes:
|
|
259
|
+
* ```typescript
|
|
260
|
+
* const safe = HtmlSanitizer.sanitizeForAttribute(userInput);
|
|
261
|
+
* html = `<div data-value="${safe}">...</div>`;
|
|
262
|
+
* ```
|
|
263
|
+
*
|
|
264
|
+
* 3. HTML CONTENT
|
|
265
|
+
* Escape text to be displayed as-is in HTML:
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const safe = HtmlSanitizer.sanitizeForHtml(userInput);
|
|
268
|
+
* html = `<p>${safe}</p>`;
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* 4. ALLOWED TAGS
|
|
272
|
+
* Choose the minimum set of tags needed:
|
|
273
|
+
* - Basic formatting: ['b', 'i', 'u', 'br']
|
|
274
|
+
* - Rich text: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
|
|
275
|
+
* - Extended: ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3']
|
|
276
|
+
*
|
|
277
|
+
* 5. URL VALIDATION
|
|
278
|
+
* URLs are automatically validated in anchor tags:
|
|
279
|
+
* - Allowed: https://, http://, mailto:, tel:, sms:, relative paths
|
|
280
|
+
* - Blocked: javascript:, data:, vbscript:, file:, blob:, about:
|
|
281
|
+
*
|
|
282
|
+
* 6. PERFORMANCE
|
|
283
|
+
* - Input size limited to 1MB (DoS protection)
|
|
284
|
+
* - Use stripAll() for better performance when HTML not needed
|
|
285
|
+
* - Cache sanitized content when possible
|
|
286
|
+
*
|
|
287
|
+
* SECURITY NOTES
|
|
288
|
+
*
|
|
289
|
+
* - XSS Prevention: All dangerous protocols and scripts are blocked
|
|
290
|
+
* - DoS Protection: Input size limits prevent resource exhaustion
|
|
291
|
+
* - Context-Aware: Different methods for different contexts (attributes vs content)
|
|
292
|
+
* - Defense in Depth: Multiple layers of validation and sanitization
|
|
293
|
+
* - Logging: Security events are logged to console for monitoring
|
|
294
|
+
*
|
|
295
|
+
* LIMITATIONS
|
|
296
|
+
*
|
|
297
|
+
* - Does not validate HTML structure (unclosed tags, nesting rules)
|
|
298
|
+
* - Does not support CSS sanitization
|
|
299
|
+
* - Does not support SVG sanitization
|
|
300
|
+
* - Maximum input size: 1MB
|
|
301
|
+
* - Maximum URL length: 2048 characters
|
|
302
|
+
*
|
|
303
|
+
* @see https://owasp.org/www-community/attacks/xss/
|
|
304
|
+
* @see https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
|
|
305
|
+
*/
|
|
306
|
+
//# sourceMappingURL=HtmlSanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HtmlSanitizer.d.ts","sourceRoot":"","sources":["../../src/sanitizer/HtmlSanitizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAeH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAmCD;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,aAAa;IAKhC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM;IA2IzD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI9C;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,oBAAoB,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,MAAM;IA4CT;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAkCzC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQrC;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA2BjD;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA6B5C;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IA+EvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0CG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAuI3C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAqFhD;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG"}
|