@morojs/moro 1.0.0 → 1.0.2
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/README.md +1 -1
- package/dist/core/config/index.d.ts +5 -5
- package/dist/core/config/index.js +1 -1
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/loader.d.ts +1 -1
- package/dist/core/config/loader.js +58 -82
- package/dist/core/config/loader.js.map +1 -1
- package/dist/core/config/schema.d.ts +1 -1
- package/dist/core/config/schema.js +52 -111
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +2 -2
- package/dist/core/config/utils.js +18 -18
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/database/adapters/drizzle.d.ts +1 -1
- package/dist/core/database/adapters/drizzle.js +39 -55
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/index.d.ts +7 -7
- package/dist/core/database/adapters/index.js +11 -11
- package/dist/core/database/adapters/index.js.map +1 -1
- package/dist/core/database/adapters/mongodb.d.ts +1 -1
- package/dist/core/database/adapters/mongodb.js +19 -23
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +1 -1
- package/dist/core/database/adapters/mysql.js +31 -27
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.d.ts +1 -1
- package/dist/core/database/adapters/postgresql.js +27 -35
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.d.ts +1 -1
- package/dist/core/database/adapters/redis.js +24 -24
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sqlite.d.ts +1 -1
- package/dist/core/database/adapters/sqlite.js +36 -36
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/database/index.d.ts +2 -2
- package/dist/core/docs/index.d.ts +7 -7
- package/dist/core/docs/index.js +13 -15
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.d.ts +5 -5
- package/dist/core/docs/openapi-generator.js +93 -94
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/simple-docs.d.ts +1 -1
- package/dist/core/docs/simple-docs.js +25 -28
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.d.ts +2 -2
- package/dist/core/docs/swagger-ui.js +46 -51
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.d.ts +1 -1
- package/dist/core/docs/zod-to-openapi.js +115 -125
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/events/event-bus.d.ts +1 -1
- package/dist/core/events/event-bus.js +15 -21
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/events/index.d.ts +2 -2
- package/dist/core/framework.d.ts +5 -5
- package/dist/core/framework.js +55 -60
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +2 -2
- package/dist/core/http/http-server.js +228 -261
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/index.d.ts +3 -3
- package/dist/core/http/router.d.ts +1 -1
- package/dist/core/http/router.js +15 -17
- package/dist/core/http/router.js.map +1 -1
- package/dist/core/logger/filters.d.ts +1 -1
- package/dist/core/logger/filters.js +16 -16
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.d.ts +3 -3
- package/dist/core/logger/logger.d.ts +1 -1
- package/dist/core/logger/logger.js +48 -59
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.d.ts +4 -4
- package/dist/core/logger/outputs.js +16 -20
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/file.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cache/file.js +19 -19
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/index.d.ts +4 -4
- package/dist/core/middleware/built-in/adapters/cache/index.js +3 -3
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.js +5 -5
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.js +18 -18
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.js +8 -8
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js +14 -14
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.d.ts +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js +13 -15
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +1 -1
- package/dist/core/middleware/built-in/adapters/cdn/index.d.ts +4 -4
- package/dist/core/middleware/built-in/adapters/cdn/index.js +3 -3
- package/dist/core/middleware/built-in/adapters/index.d.ts +4 -4
- package/dist/core/middleware/built-in/auth.d.ts +1 -1
- package/dist/core/middleware/built-in/auth.js +14 -14
- package/dist/core/middleware/built-in/cache.d.ts +2 -2
- package/dist/core/middleware/built-in/cache.js +43 -45
- package/dist/core/middleware/built-in/cache.js.map +1 -1
- package/dist/core/middleware/built-in/cdn.d.ts +2 -2
- package/dist/core/middleware/built-in/cdn.js +27 -29
- package/dist/core/middleware/built-in/cdn.js.map +1 -1
- package/dist/core/middleware/built-in/cookie.d.ts +2 -2
- package/dist/core/middleware/built-in/cookie.js +17 -17
- package/dist/core/middleware/built-in/cookie.js.map +1 -1
- package/dist/core/middleware/built-in/cors.d.ts +1 -1
- package/dist/core/middleware/built-in/cors.js +13 -13
- package/dist/core/middleware/built-in/csp.d.ts +1 -1
- package/dist/core/middleware/built-in/csp.js +22 -25
- package/dist/core/middleware/built-in/csp.js.map +1 -1
- package/dist/core/middleware/built-in/csrf.d.ts +1 -1
- package/dist/core/middleware/built-in/csrf.js +21 -24
- package/dist/core/middleware/built-in/csrf.js.map +1 -1
- package/dist/core/middleware/built-in/error-tracker.js +2 -2
- package/dist/core/middleware/built-in/index.d.ts +14 -14
- package/dist/core/middleware/built-in/performance-monitor.js +2 -2
- package/dist/core/middleware/built-in/rate-limit.d.ts +1 -1
- package/dist/core/middleware/built-in/rate-limit.js +12 -12
- package/dist/core/middleware/built-in/request-logger.js.map +1 -1
- package/dist/core/middleware/built-in/session.d.ts +5 -5
- package/dist/core/middleware/built-in/session.js +35 -38
- package/dist/core/middleware/built-in/session.js.map +1 -1
- package/dist/core/middleware/built-in/sse.d.ts +1 -1
- package/dist/core/middleware/built-in/sse.js +20 -22
- package/dist/core/middleware/built-in/sse.js.map +1 -1
- package/dist/core/middleware/built-in/validation.d.ts +1 -1
- package/dist/core/middleware/built-in/validation.js +13 -13
- package/dist/core/middleware/index.d.ts +5 -5
- package/dist/core/middleware/index.js +16 -16
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +2 -2
- package/dist/core/modules/auto-discovery.js +12 -13
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/index.d.ts +2 -2
- package/dist/core/modules/index.js.map +1 -1
- package/dist/core/modules/modules.d.ts +3 -3
- package/dist/core/modules/modules.js +3 -6
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/index.d.ts +2 -2
- package/dist/core/networking/index.js.map +1 -1
- package/dist/core/networking/service-discovery.d.ts +2 -2
- package/dist/core/networking/service-discovery.js +27 -27
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/networking/websocket-manager.d.ts +3 -3
- package/dist/core/networking/websocket-manager.js +15 -16
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/routing/app-integration.d.ts +2 -2
- package/dist/core/routing/app-integration.js +13 -13
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +3 -3
- package/dist/core/routing/index.js +43 -52
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/runtime/aws-lambda-adapter.d.ts +3 -3
- package/dist/core/runtime/aws-lambda-adapter.js +14 -16
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.d.ts +2 -2
- package/dist/core/runtime/base-adapter.js +11 -12
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.d.ts +3 -3
- package/dist/core/runtime/cloudflare-workers-adapter.js +20 -21
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/index.d.ts +9 -9
- package/dist/core/runtime/index.js +4 -4
- package/dist/core/runtime/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +5 -5
- package/dist/core/runtime/node-adapter.js +35 -35
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.d.ts +3 -3
- package/dist/core/runtime/vercel-edge-adapter.js +12 -15
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.js +6 -6
- package/dist/core/utilities/container.d.ts +1 -1
- package/dist/core/utilities/container.js +17 -22
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.d.ts +3 -3
- package/dist/core/utilities/hooks.js +11 -11
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/utilities/index.d.ts +4 -4
- package/dist/core/validation/index.d.ts +3 -3
- package/dist/core/validation/index.js +15 -15
- package/dist/core/validation/index.js.map +1 -1
- package/dist/index.d.ts +41 -30
- package/dist/index.js +50 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +14 -14
- package/dist/moro.js +79 -88
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +1 -1
- package/dist/types/core.d.ts +2 -2
- package/dist/types/events.d.ts +19 -19
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/http.d.ts +2 -2
- package/dist/types/logger.d.ts +3 -3
- package/dist/types/module.d.ts +2 -2
- package/dist/types/runtime.d.ts +2 -2
- package/dist/types/session.d.ts +4 -4
- package/package.json +183 -165
- package/src/core/config/index.ts +7 -9
- package/src/core/config/loader.ts +86 -158
- package/src/core/config/schema.ts +59 -122
- package/src/core/config/utils.ts +27 -45
- package/src/core/database/adapters/drizzle.ts +53 -75
- package/src/core/database/adapters/index.ts +26 -29
- package/src/core/database/adapters/mongodb.ts +31 -54
- package/src/core/database/adapters/mysql.ts +40 -50
- package/src/core/database/adapters/postgresql.ts +32 -42
- package/src/core/database/adapters/redis.ts +31 -36
- package/src/core/database/adapters/sqlite.ts +43 -51
- package/src/core/database/index.ts +2 -2
- package/src/core/docs/index.ts +25 -39
- package/src/core/docs/openapi-generator.ts +104 -117
- package/src/core/docs/simple-docs.ts +29 -39
- package/src/core/docs/swagger-ui.ts +57 -76
- package/src/core/docs/zod-to-openapi.ts +121 -153
- package/src/core/events/event-bus.ts +22 -45
- package/src/core/events/index.ts +2 -2
- package/src/core/framework.ts +119 -197
- package/src/core/http/http-server.ts +260 -360
- package/src/core/http/index.ts +3 -8
- package/src/core/http/router.ts +19 -31
- package/src/core/logger/filters.ts +19 -22
- package/src/core/logger/index.ts +3 -3
- package/src/core/logger/logger.ts +59 -100
- package/src/core/logger/outputs.ts +23 -27
- package/src/core/middleware/built-in/adapters/cache/file.ts +21 -23
- package/src/core/middleware/built-in/adapters/cache/index.ts +11 -14
- package/src/core/middleware/built-in/adapters/cache/memory.ts +7 -7
- package/src/core/middleware/built-in/adapters/cache/redis.ts +21 -24
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +10 -18
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +19 -36
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +17 -26
- package/src/core/middleware/built-in/adapters/cdn/index.ts +10 -10
- package/src/core/middleware/built-in/adapters/index.ts +4 -4
- package/src/core/middleware/built-in/auth.ts +16 -16
- package/src/core/middleware/built-in/cache.ts +50 -67
- package/src/core/middleware/built-in/cdn.ts +34 -61
- package/src/core/middleware/built-in/cookie.ts +23 -28
- package/src/core/middleware/built-in/cors.ts +17 -17
- package/src/core/middleware/built-in/csp.ts +25 -31
- package/src/core/middleware/built-in/csrf.ts +24 -29
- package/src/core/middleware/built-in/error-tracker.ts +3 -3
- package/src/core/middleware/built-in/index.ts +28 -28
- package/src/core/middleware/built-in/performance-monitor.ts +4 -4
- package/src/core/middleware/built-in/rate-limit.ts +15 -15
- package/src/core/middleware/built-in/request-logger.ts +1 -3
- package/src/core/middleware/built-in/session.ts +47 -70
- package/src/core/middleware/built-in/sse.ts +23 -28
- package/src/core/middleware/built-in/validation.ts +15 -15
- package/src/core/middleware/index.ts +26 -37
- package/src/core/modules/auto-discovery.ts +21 -31
- package/src/core/modules/index.ts +2 -5
- package/src/core/modules/modules.ts +11 -20
- package/src/core/networking/index.ts +2 -6
- package/src/core/networking/service-discovery.ts +41 -61
- package/src/core/networking/websocket-manager.ts +27 -36
- package/src/core/routing/app-integration.ts +19 -32
- package/src/core/routing/index.ts +57 -88
- package/src/core/runtime/aws-lambda-adapter.ts +20 -30
- package/src/core/runtime/base-adapter.ts +17 -27
- package/src/core/runtime/cloudflare-workers-adapter.ts +28 -42
- package/src/core/runtime/index.ts +21 -33
- package/src/core/runtime/node-adapter.ts +59 -73
- package/src/core/runtime/vercel-edge-adapter.ts +18 -29
- package/src/core/utilities/circuit-breaker.ts +7 -7
- package/src/core/utilities/container.ts +52 -89
- package/src/core/utilities/hooks.ts +17 -23
- package/src/core/utilities/index.ts +4 -4
- package/src/core/validation/index.ts +25 -51
- package/src/index.ts +104 -60
- package/src/moro.ts +119 -191
- package/src/types/cache.ts +1 -1
- package/src/types/core.ts +2 -2
- package/src/types/database.ts +2 -10
- package/src/types/events.ts +23 -31
- package/src/types/hooks.ts +1 -1
- package/src/types/http.ts +5 -8
- package/src/types/logger.ts +7 -23
- package/src/types/module.ts +2 -2
- package/src/types/runtime.ts +6 -21
- package/src/types/session.ts +4 -4
|
@@ -48,7 +48,7 @@ class MoroHttpServer {
|
|
|
48
48
|
this.globalMiddleware = [];
|
|
49
49
|
this.compressionEnabled = true;
|
|
50
50
|
this.compressionThreshold = 1024;
|
|
51
|
-
this.logger = (0, logger_1.createFrameworkLogger)(
|
|
51
|
+
this.logger = (0, logger_1.createFrameworkLogger)('HttpServer');
|
|
52
52
|
this.server = (0, http_1.createServer)(this.handleRequest.bind(this));
|
|
53
53
|
}
|
|
54
54
|
// Middleware management
|
|
@@ -57,19 +57,19 @@ class MoroHttpServer {
|
|
|
57
57
|
}
|
|
58
58
|
// Routing methods
|
|
59
59
|
get(path, ...handlers) {
|
|
60
|
-
this.addRoute(
|
|
60
|
+
this.addRoute('GET', path, handlers);
|
|
61
61
|
}
|
|
62
62
|
post(path, ...handlers) {
|
|
63
|
-
this.addRoute(
|
|
63
|
+
this.addRoute('POST', path, handlers);
|
|
64
64
|
}
|
|
65
65
|
put(path, ...handlers) {
|
|
66
|
-
this.addRoute(
|
|
66
|
+
this.addRoute('PUT', path, handlers);
|
|
67
67
|
}
|
|
68
68
|
delete(path, ...handlers) {
|
|
69
|
-
this.addRoute(
|
|
69
|
+
this.addRoute('DELETE', path, handlers);
|
|
70
70
|
}
|
|
71
71
|
patch(path, ...handlers) {
|
|
72
|
-
this.addRoute(
|
|
72
|
+
this.addRoute('PATCH', path, handlers);
|
|
73
73
|
}
|
|
74
74
|
addRoute(method, path, handlers) {
|
|
75
75
|
const { pattern, paramNames } = this.pathToRegex(path);
|
|
@@ -90,9 +90,9 @@ class MoroHttpServer {
|
|
|
90
90
|
const regexPattern = path
|
|
91
91
|
.replace(/\/:([^/]+)/g, (match, paramName) => {
|
|
92
92
|
paramNames.push(paramName);
|
|
93
|
-
return
|
|
93
|
+
return '/([^/]+)';
|
|
94
94
|
})
|
|
95
|
-
.replace(/\//g,
|
|
95
|
+
.replace(/\//g, '\\/');
|
|
96
96
|
return {
|
|
97
97
|
pattern: new RegExp(`^${regexPattern}$`),
|
|
98
98
|
paramNames,
|
|
@@ -107,7 +107,7 @@ class MoroHttpServer {
|
|
|
107
107
|
httpReq.path = url.pathname;
|
|
108
108
|
httpReq.query = Object.fromEntries(url.searchParams);
|
|
109
109
|
// Parse body for POST/PUT/PATCH requests
|
|
110
|
-
if ([
|
|
110
|
+
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
|
|
111
111
|
httpReq.body = await this.parseBody(req);
|
|
112
112
|
}
|
|
113
113
|
// Execute global middleware first
|
|
@@ -119,7 +119,7 @@ class MoroHttpServer {
|
|
|
119
119
|
// Find matching route
|
|
120
120
|
const route = this.findRoute(req.method, httpReq.path);
|
|
121
121
|
if (!route) {
|
|
122
|
-
httpRes.status(404).json({ success: false, error:
|
|
122
|
+
httpRes.status(404).json({ success: false, error: 'Not found' });
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
// Extract path parameters
|
|
@@ -136,7 +136,7 @@ class MoroHttpServer {
|
|
|
136
136
|
await route.handler(httpReq, httpRes);
|
|
137
137
|
}
|
|
138
138
|
catch (error) {
|
|
139
|
-
this.logger.error(
|
|
139
|
+
this.logger.error('Request error', 'RequestHandler', {
|
|
140
140
|
error: error instanceof Error ? error.message : String(error),
|
|
141
141
|
requestId: httpReq.requestId,
|
|
142
142
|
method: req.method,
|
|
@@ -145,7 +145,7 @@ class MoroHttpServer {
|
|
|
145
145
|
if (!httpRes.headersSent) {
|
|
146
146
|
httpRes.status(500).json({
|
|
147
147
|
success: false,
|
|
148
|
-
error:
|
|
148
|
+
error: 'Internal server error',
|
|
149
149
|
requestId: httpReq.requestId,
|
|
150
150
|
});
|
|
151
151
|
}
|
|
@@ -156,20 +156,20 @@ class MoroHttpServer {
|
|
|
156
156
|
httpReq.params = {};
|
|
157
157
|
httpReq.query = {};
|
|
158
158
|
httpReq.body = null;
|
|
159
|
-
httpReq.path =
|
|
160
|
-
httpReq.ip = req.socket.remoteAddress ||
|
|
159
|
+
httpReq.path = '';
|
|
160
|
+
httpReq.ip = req.socket.remoteAddress || '';
|
|
161
161
|
httpReq.requestId = Math.random().toString(36).substring(7);
|
|
162
162
|
httpReq.headers = req.headers;
|
|
163
163
|
// Parse cookies
|
|
164
|
-
httpReq.cookies = this.parseCookies(req.headers.cookie ||
|
|
164
|
+
httpReq.cookies = this.parseCookies(req.headers.cookie || '');
|
|
165
165
|
return httpReq;
|
|
166
166
|
}
|
|
167
167
|
parseCookies(cookieHeader) {
|
|
168
168
|
const cookies = {};
|
|
169
169
|
if (!cookieHeader)
|
|
170
170
|
return cookies;
|
|
171
|
-
cookieHeader.split(
|
|
172
|
-
const [name, value] = cookie.trim().split(
|
|
171
|
+
cookieHeader.split(';').forEach(cookie => {
|
|
172
|
+
const [name, value] = cookie.trim().split('=');
|
|
173
173
|
if (name && value) {
|
|
174
174
|
cookies[name] = decodeURIComponent(value);
|
|
175
175
|
}
|
|
@@ -183,27 +183,26 @@ class MoroHttpServer {
|
|
|
183
183
|
return;
|
|
184
184
|
const jsonString = JSON.stringify(data);
|
|
185
185
|
const buffer = Buffer.from(jsonString);
|
|
186
|
-
httpRes.setHeader(
|
|
186
|
+
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
187
187
|
// Compression
|
|
188
|
-
if (this.compressionEnabled &&
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (acceptEncoding.includes("gzip")) {
|
|
188
|
+
if (this.compressionEnabled && buffer.length > this.compressionThreshold) {
|
|
189
|
+
const acceptEncoding = httpRes.req.headers['accept-encoding'] || '';
|
|
190
|
+
if (acceptEncoding.includes('gzip')) {
|
|
192
191
|
const compressed = await gzip(buffer);
|
|
193
|
-
httpRes.setHeader(
|
|
194
|
-
httpRes.setHeader(
|
|
192
|
+
httpRes.setHeader('Content-Encoding', 'gzip');
|
|
193
|
+
httpRes.setHeader('Content-Length', compressed.length);
|
|
195
194
|
httpRes.end(compressed);
|
|
196
195
|
return;
|
|
197
196
|
}
|
|
198
|
-
else if (acceptEncoding.includes(
|
|
197
|
+
else if (acceptEncoding.includes('deflate')) {
|
|
199
198
|
const compressed = await deflate(buffer);
|
|
200
|
-
httpRes.setHeader(
|
|
201
|
-
httpRes.setHeader(
|
|
199
|
+
httpRes.setHeader('Content-Encoding', 'deflate');
|
|
200
|
+
httpRes.setHeader('Content-Length', compressed.length);
|
|
202
201
|
httpRes.end(compressed);
|
|
203
202
|
return;
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
|
-
httpRes.setHeader(
|
|
205
|
+
httpRes.setHeader('Content-Length', buffer.length);
|
|
207
206
|
httpRes.end(buffer);
|
|
208
207
|
};
|
|
209
208
|
httpRes.status = (code) => {
|
|
@@ -214,21 +213,21 @@ class MoroHttpServer {
|
|
|
214
213
|
if (httpRes.headersSent)
|
|
215
214
|
return;
|
|
216
215
|
// Auto-detect content type if not already set
|
|
217
|
-
if (!httpRes.getHeader(
|
|
218
|
-
if (typeof data ===
|
|
216
|
+
if (!httpRes.getHeader('Content-Type')) {
|
|
217
|
+
if (typeof data === 'string') {
|
|
219
218
|
// Check if it's JSON
|
|
220
219
|
try {
|
|
221
220
|
JSON.parse(data);
|
|
222
|
-
httpRes.setHeader(
|
|
221
|
+
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
223
222
|
}
|
|
224
223
|
catch {
|
|
225
224
|
// Default to plain text
|
|
226
|
-
httpRes.setHeader(
|
|
225
|
+
httpRes.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
227
226
|
}
|
|
228
227
|
}
|
|
229
228
|
else {
|
|
230
229
|
// Buffer data - default to octet-stream
|
|
231
|
-
httpRes.setHeader(
|
|
230
|
+
httpRes.setHeader('Content-Type', 'application/octet-stream');
|
|
232
231
|
}
|
|
233
232
|
}
|
|
234
233
|
httpRes.end(data);
|
|
@@ -241,90 +240,90 @@ class MoroHttpServer {
|
|
|
241
240
|
if (options.expires)
|
|
242
241
|
cookieString += `; Expires=${options.expires.toUTCString()}`;
|
|
243
242
|
if (options.httpOnly)
|
|
244
|
-
cookieString +=
|
|
243
|
+
cookieString += '; HttpOnly';
|
|
245
244
|
if (options.secure)
|
|
246
|
-
cookieString +=
|
|
245
|
+
cookieString += '; Secure';
|
|
247
246
|
if (options.sameSite)
|
|
248
247
|
cookieString += `; SameSite=${options.sameSite}`;
|
|
249
248
|
if (options.domain)
|
|
250
249
|
cookieString += `; Domain=${options.domain}`;
|
|
251
250
|
if (options.path)
|
|
252
251
|
cookieString += `; Path=${options.path}`;
|
|
253
|
-
const existingCookies = httpRes.getHeader(
|
|
252
|
+
const existingCookies = httpRes.getHeader('Set-Cookie') || [];
|
|
254
253
|
const cookies = Array.isArray(existingCookies)
|
|
255
254
|
? [...existingCookies]
|
|
256
255
|
: [existingCookies];
|
|
257
256
|
cookies.push(cookieString);
|
|
258
|
-
httpRes.setHeader(
|
|
257
|
+
httpRes.setHeader('Set-Cookie', cookies);
|
|
259
258
|
return httpRes;
|
|
260
259
|
};
|
|
261
260
|
httpRes.clearCookie = (name, options = {}) => {
|
|
262
261
|
const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
|
|
263
|
-
return httpRes.cookie(name,
|
|
262
|
+
return httpRes.cookie(name, '', clearOptions);
|
|
264
263
|
};
|
|
265
264
|
httpRes.redirect = (url, status = 302) => {
|
|
266
265
|
if (httpRes.headersSent)
|
|
267
266
|
return;
|
|
268
267
|
httpRes.statusCode = status;
|
|
269
|
-
httpRes.setHeader(
|
|
268
|
+
httpRes.setHeader('Location', url);
|
|
270
269
|
httpRes.end();
|
|
271
270
|
};
|
|
272
271
|
httpRes.sendFile = async (filePath) => {
|
|
273
272
|
if (httpRes.headersSent)
|
|
274
273
|
return;
|
|
275
274
|
try {
|
|
276
|
-
const fs = await Promise.resolve().then(() => __importStar(require(
|
|
277
|
-
const path = await Promise.resolve().then(() => __importStar(require(
|
|
275
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
276
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
278
277
|
const extension = path.extname(filePath);
|
|
279
278
|
const mime = await this.getMimeType(extension);
|
|
280
279
|
const stats = await fs.stat(filePath);
|
|
281
280
|
const data = await fs.readFile(filePath);
|
|
282
281
|
// Add charset for text-based files
|
|
283
282
|
const contentType = this.addCharsetIfNeeded(mime);
|
|
284
|
-
httpRes.setHeader(
|
|
285
|
-
httpRes.setHeader(
|
|
283
|
+
httpRes.setHeader('Content-Type', contentType);
|
|
284
|
+
httpRes.setHeader('Content-Length', stats.size);
|
|
286
285
|
// Add security headers for file downloads
|
|
287
|
-
httpRes.setHeader(
|
|
286
|
+
httpRes.setHeader('X-Content-Type-Options', 'nosniff');
|
|
288
287
|
// Add caching headers
|
|
289
|
-
httpRes.setHeader(
|
|
290
|
-
httpRes.setHeader(
|
|
288
|
+
httpRes.setHeader('Last-Modified', stats.mtime.toUTCString());
|
|
289
|
+
httpRes.setHeader('Cache-Control', 'public, max-age=31536000'); // 1 year for static files
|
|
291
290
|
httpRes.end(data);
|
|
292
291
|
}
|
|
293
292
|
catch (error) {
|
|
294
|
-
httpRes.status(404).json({ success: false, error:
|
|
293
|
+
httpRes.status(404).json({ success: false, error: 'File not found' });
|
|
295
294
|
}
|
|
296
295
|
};
|
|
297
296
|
return httpRes;
|
|
298
297
|
}
|
|
299
298
|
async getMimeType(ext) {
|
|
300
299
|
const mimeTypes = {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
300
|
+
'.html': 'text/html',
|
|
301
|
+
'.css': 'text/css',
|
|
302
|
+
'.js': 'application/javascript',
|
|
303
|
+
'.json': 'application/json',
|
|
304
|
+
'.png': 'image/png',
|
|
305
|
+
'.jpg': 'image/jpeg',
|
|
306
|
+
'.jpeg': 'image/jpeg',
|
|
307
|
+
'.gif': 'image/gif',
|
|
308
|
+
'.svg': 'image/svg+xml',
|
|
309
|
+
'.ico': 'image/x-icon',
|
|
310
|
+
'.pdf': 'application/pdf',
|
|
311
|
+
'.txt': 'text/plain',
|
|
312
|
+
'.xml': 'application/xml',
|
|
314
313
|
};
|
|
315
|
-
return mimeTypes[ext.toLowerCase()] ||
|
|
314
|
+
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
316
315
|
}
|
|
317
316
|
addCharsetIfNeeded(mimeType) {
|
|
318
317
|
// Add charset for text-based content types
|
|
319
318
|
const textTypes = [
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
'text/',
|
|
320
|
+
'application/json',
|
|
321
|
+
'application/javascript',
|
|
322
|
+
'application/xml',
|
|
323
|
+
'image/svg+xml',
|
|
325
324
|
];
|
|
326
|
-
const needsCharset = textTypes.some(
|
|
327
|
-
if (needsCharset && !mimeType.includes(
|
|
325
|
+
const needsCharset = textTypes.some(type => mimeType.startsWith(type));
|
|
326
|
+
if (needsCharset && !mimeType.includes('charset')) {
|
|
328
327
|
return `${mimeType}; charset=utf-8`;
|
|
329
328
|
}
|
|
330
329
|
return mimeType;
|
|
@@ -334,25 +333,25 @@ class MoroHttpServer {
|
|
|
334
333
|
const chunks = [];
|
|
335
334
|
let totalLength = 0;
|
|
336
335
|
const maxSize = 10 * 1024 * 1024; // 10MB limit
|
|
337
|
-
req.on(
|
|
336
|
+
req.on('data', (chunk) => {
|
|
338
337
|
totalLength += chunk.length;
|
|
339
338
|
if (totalLength > maxSize) {
|
|
340
|
-
reject(new Error(
|
|
339
|
+
reject(new Error('Request body too large'));
|
|
341
340
|
return;
|
|
342
341
|
}
|
|
343
342
|
chunks.push(chunk);
|
|
344
343
|
});
|
|
345
|
-
req.on(
|
|
344
|
+
req.on('end', () => {
|
|
346
345
|
try {
|
|
347
346
|
const body = Buffer.concat(chunks);
|
|
348
|
-
const contentType = req.headers[
|
|
349
|
-
if (contentType.includes(
|
|
347
|
+
const contentType = req.headers['content-type'] || '';
|
|
348
|
+
if (contentType.includes('application/json')) {
|
|
350
349
|
resolve(JSON.parse(body.toString()));
|
|
351
350
|
}
|
|
352
|
-
else if (contentType.includes(
|
|
351
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
353
352
|
resolve(this.parseUrlEncoded(body.toString()));
|
|
354
353
|
}
|
|
355
|
-
else if (contentType.includes(
|
|
354
|
+
else if (contentType.includes('multipart/form-data')) {
|
|
356
355
|
resolve(this.parseMultipart(body, contentType));
|
|
357
356
|
}
|
|
358
357
|
else {
|
|
@@ -363,20 +362,20 @@ class MoroHttpServer {
|
|
|
363
362
|
reject(error);
|
|
364
363
|
}
|
|
365
364
|
});
|
|
366
|
-
req.on(
|
|
365
|
+
req.on('error', reject);
|
|
367
366
|
});
|
|
368
367
|
}
|
|
369
368
|
parseMultipart(buffer, contentType) {
|
|
370
|
-
const boundary = contentType.split(
|
|
369
|
+
const boundary = contentType.split('boundary=')[1];
|
|
371
370
|
if (!boundary) {
|
|
372
|
-
throw new Error(
|
|
371
|
+
throw new Error('Invalid multipart boundary');
|
|
373
372
|
}
|
|
374
|
-
const parts = buffer.toString(
|
|
373
|
+
const parts = buffer.toString('binary').split('--' + boundary);
|
|
375
374
|
const fields = {};
|
|
376
375
|
const files = {};
|
|
377
376
|
for (let i = 1; i < parts.length - 1; i++) {
|
|
378
377
|
const part = parts[i];
|
|
379
|
-
const [headers, content] = part.split(
|
|
378
|
+
const [headers, content] = part.split('\r\n\r\n');
|
|
380
379
|
if (!headers || content === undefined)
|
|
381
380
|
continue;
|
|
382
381
|
const nameMatch = headers.match(/name="([^"]+)"/);
|
|
@@ -387,15 +386,13 @@ class MoroHttpServer {
|
|
|
387
386
|
if (filenameMatch) {
|
|
388
387
|
// This is a file
|
|
389
388
|
const filename = filenameMatch[1];
|
|
390
|
-
const mimeType = contentTypeMatch
|
|
391
|
-
? contentTypeMatch[1]
|
|
392
|
-
: "application/octet-stream";
|
|
389
|
+
const mimeType = contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream';
|
|
393
390
|
const fileContent = content.substring(0, content.length - 2); // Remove trailing \r\n
|
|
394
391
|
files[name] = {
|
|
395
392
|
filename,
|
|
396
393
|
mimetype: mimeType,
|
|
397
|
-
data: Buffer.from(fileContent,
|
|
398
|
-
size: Buffer.byteLength(fileContent,
|
|
394
|
+
data: Buffer.from(fileContent, 'binary'),
|
|
395
|
+
size: Buffer.byteLength(fileContent, 'binary'),
|
|
399
396
|
};
|
|
400
397
|
}
|
|
401
398
|
else {
|
|
@@ -415,7 +412,7 @@ class MoroHttpServer {
|
|
|
415
412
|
return result;
|
|
416
413
|
}
|
|
417
414
|
findRoute(method, path) {
|
|
418
|
-
return
|
|
415
|
+
return this.routes.find(route => route.method === method && route.pattern.test(path)) || null;
|
|
419
416
|
}
|
|
420
417
|
async executeMiddleware(middleware, req, res) {
|
|
421
418
|
for (const mw of middleware) {
|
|
@@ -447,7 +444,7 @@ class MoroHttpServer {
|
|
|
447
444
|
}
|
|
448
445
|
listen(port, host, callback) {
|
|
449
446
|
// Handle overloaded parameters (port, callback) or (port, host, callback)
|
|
450
|
-
if (typeof host ===
|
|
447
|
+
if (typeof host === 'function') {
|
|
451
448
|
callback = host;
|
|
452
449
|
host = undefined;
|
|
453
450
|
}
|
|
@@ -459,7 +456,7 @@ class MoroHttpServer {
|
|
|
459
456
|
}
|
|
460
457
|
}
|
|
461
458
|
close() {
|
|
462
|
-
return new Promise(
|
|
459
|
+
return new Promise(resolve => {
|
|
463
460
|
this.server.close(() => resolve());
|
|
464
461
|
});
|
|
465
462
|
}
|
|
@@ -472,14 +469,14 @@ exports.MoroHttpServer = MoroHttpServer;
|
|
|
472
469
|
exports.middleware = {
|
|
473
470
|
cors: (options = {}) => {
|
|
474
471
|
return (req, res, next) => {
|
|
475
|
-
res.setHeader(
|
|
476
|
-
res.setHeader(
|
|
477
|
-
res.setHeader(
|
|
472
|
+
res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
|
|
473
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
474
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
478
475
|
if (options.credentials) {
|
|
479
|
-
res.setHeader(
|
|
476
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
480
477
|
}
|
|
481
|
-
if (req.method ===
|
|
482
|
-
res.status(200).send(
|
|
478
|
+
if (req.method === 'OPTIONS') {
|
|
479
|
+
res.status(200).send('');
|
|
483
480
|
return;
|
|
484
481
|
}
|
|
485
482
|
next();
|
|
@@ -487,21 +484,21 @@ exports.middleware = {
|
|
|
487
484
|
},
|
|
488
485
|
helmet: () => {
|
|
489
486
|
return (req, res, next) => {
|
|
490
|
-
res.setHeader(
|
|
491
|
-
res.setHeader(
|
|
492
|
-
res.setHeader(
|
|
493
|
-
res.setHeader(
|
|
494
|
-
res.setHeader(
|
|
495
|
-
res.setHeader(
|
|
487
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
488
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
489
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
490
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
491
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
492
|
+
res.setHeader('Content-Security-Policy', "default-src 'self'");
|
|
496
493
|
next();
|
|
497
494
|
};
|
|
498
495
|
},
|
|
499
496
|
compression: (options = {}) => {
|
|
500
|
-
const zlib = require(
|
|
497
|
+
const zlib = require('zlib');
|
|
501
498
|
const threshold = options.threshold || 1024;
|
|
502
499
|
const level = options.level || 6;
|
|
503
500
|
return (req, res, next) => {
|
|
504
|
-
const acceptEncoding = req.headers[
|
|
501
|
+
const acceptEncoding = req.headers['accept-encoding'] || '';
|
|
505
502
|
// Override res.json to compress responses
|
|
506
503
|
const originalJson = res.json;
|
|
507
504
|
const originalSend = res.send;
|
|
@@ -509,45 +506,37 @@ exports.middleware = {
|
|
|
509
506
|
const content = isJson ? JSON.stringify(data) : data;
|
|
510
507
|
const buffer = Buffer.from(content);
|
|
511
508
|
if (buffer.length < threshold) {
|
|
512
|
-
return isJson
|
|
513
|
-
? originalJson.call(res, data)
|
|
514
|
-
: originalSend.call(res, data);
|
|
509
|
+
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
515
510
|
}
|
|
516
|
-
if (acceptEncoding.includes(
|
|
517
|
-
res.setHeader(
|
|
511
|
+
if (acceptEncoding.includes('gzip')) {
|
|
512
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
518
513
|
zlib.gzip(buffer, { level }, (err, compressed) => {
|
|
519
514
|
if (err) {
|
|
520
|
-
return isJson
|
|
521
|
-
? originalJson.call(res, data)
|
|
522
|
-
: originalSend.call(res, data);
|
|
515
|
+
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
523
516
|
}
|
|
524
|
-
res.setHeader(
|
|
517
|
+
res.setHeader('Content-Length', compressed.length);
|
|
525
518
|
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
526
519
|
res.end(compressed);
|
|
527
520
|
});
|
|
528
521
|
}
|
|
529
|
-
else if (acceptEncoding.includes(
|
|
530
|
-
res.setHeader(
|
|
522
|
+
else if (acceptEncoding.includes('deflate')) {
|
|
523
|
+
res.setHeader('Content-Encoding', 'deflate');
|
|
531
524
|
zlib.deflate(buffer, { level }, (err, compressed) => {
|
|
532
525
|
if (err) {
|
|
533
|
-
return isJson
|
|
534
|
-
? originalJson.call(res, data)
|
|
535
|
-
: originalSend.call(res, data);
|
|
526
|
+
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
536
527
|
}
|
|
537
|
-
res.setHeader(
|
|
528
|
+
res.setHeader('Content-Length', compressed.length);
|
|
538
529
|
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
539
530
|
res.end(compressed);
|
|
540
531
|
});
|
|
541
532
|
}
|
|
542
533
|
else {
|
|
543
|
-
return isJson
|
|
544
|
-
? originalJson.call(res, data)
|
|
545
|
-
: originalSend.call(res, data);
|
|
534
|
+
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
546
535
|
}
|
|
547
536
|
};
|
|
548
537
|
res.json = function (data) {
|
|
549
538
|
// Ensure charset is set for Safari compatibility
|
|
550
|
-
this.setHeader(
|
|
539
|
+
this.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
551
540
|
compressResponse(data, true);
|
|
552
541
|
return this;
|
|
553
542
|
};
|
|
@@ -561,7 +550,7 @@ exports.middleware = {
|
|
|
561
550
|
requestLogger: () => {
|
|
562
551
|
return (req, res, next) => {
|
|
563
552
|
const start = Date.now();
|
|
564
|
-
res.on(
|
|
553
|
+
res.on('finish', () => {
|
|
565
554
|
const duration = Date.now() - start;
|
|
566
555
|
// Request completed - logged by framework
|
|
567
556
|
});
|
|
@@ -569,14 +558,14 @@ exports.middleware = {
|
|
|
569
558
|
};
|
|
570
559
|
},
|
|
571
560
|
bodySize: (options = {}) => {
|
|
572
|
-
const limit = options.limit ||
|
|
561
|
+
const limit = options.limit || '10mb';
|
|
573
562
|
const limitBytes = parseSize(limit);
|
|
574
563
|
return (req, res, next) => {
|
|
575
|
-
const contentLength = parseInt(req.headers[
|
|
564
|
+
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
576
565
|
if (contentLength > limitBytes) {
|
|
577
566
|
res.status(413).json({
|
|
578
567
|
success: false,
|
|
579
|
-
error:
|
|
568
|
+
error: 'Request entity too large',
|
|
580
569
|
limit: limit,
|
|
581
570
|
});
|
|
582
571
|
return;
|
|
@@ -587,28 +576,28 @@ exports.middleware = {
|
|
|
587
576
|
static: (options) => {
|
|
588
577
|
return async (req, res, next) => {
|
|
589
578
|
// Only handle GET and HEAD requests
|
|
590
|
-
if (req.method !==
|
|
579
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
591
580
|
next();
|
|
592
581
|
return;
|
|
593
582
|
}
|
|
594
583
|
try {
|
|
595
|
-
const fs = await Promise.resolve().then(() => __importStar(require(
|
|
596
|
-
const path = await Promise.resolve().then(() => __importStar(require(
|
|
597
|
-
const crypto = await Promise.resolve().then(() => __importStar(require(
|
|
584
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
585
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
586
|
+
const crypto = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
598
587
|
let filePath = path.join(options.root, req.path);
|
|
599
588
|
// Security: prevent directory traversal
|
|
600
589
|
if (!filePath.startsWith(path.resolve(options.root))) {
|
|
601
|
-
res.status(403).json({ success: false, error:
|
|
590
|
+
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
602
591
|
return;
|
|
603
592
|
}
|
|
604
593
|
// Handle dotfiles
|
|
605
594
|
const basename = path.basename(filePath);
|
|
606
|
-
if (basename.startsWith(
|
|
607
|
-
if (options.dotfiles ===
|
|
608
|
-
res.status(403).json({ success: false, error:
|
|
595
|
+
if (basename.startsWith('.')) {
|
|
596
|
+
if (options.dotfiles === 'deny') {
|
|
597
|
+
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
609
598
|
return;
|
|
610
599
|
}
|
|
611
|
-
else if (options.dotfiles ===
|
|
600
|
+
else if (options.dotfiles === 'ignore') {
|
|
612
601
|
next();
|
|
613
602
|
return;
|
|
614
603
|
}
|
|
@@ -623,7 +612,7 @@ exports.middleware = {
|
|
|
623
612
|
}
|
|
624
613
|
// Handle directories
|
|
625
614
|
if (stats.isDirectory()) {
|
|
626
|
-
const indexFiles = options.index || [
|
|
615
|
+
const indexFiles = options.index || ['index.html', 'index.htm'];
|
|
627
616
|
let indexFound = false;
|
|
628
617
|
for (const indexFile of indexFiles) {
|
|
629
618
|
const indexPath = path.join(filePath, indexFile);
|
|
@@ -648,48 +637,46 @@ exports.middleware = {
|
|
|
648
637
|
// Set headers with proper mime type and charset
|
|
649
638
|
const ext = path.extname(filePath);
|
|
650
639
|
const mimeTypes = {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
640
|
+
'.html': 'text/html',
|
|
641
|
+
'.css': 'text/css',
|
|
642
|
+
'.js': 'application/javascript',
|
|
643
|
+
'.json': 'application/json',
|
|
644
|
+
'.png': 'image/png',
|
|
645
|
+
'.jpg': 'image/jpeg',
|
|
646
|
+
'.jpeg': 'image/jpeg',
|
|
647
|
+
'.gif': 'image/gif',
|
|
648
|
+
'.svg': 'image/svg+xml',
|
|
649
|
+
'.ico': 'image/x-icon',
|
|
650
|
+
'.pdf': 'application/pdf',
|
|
651
|
+
'.txt': 'text/plain',
|
|
652
|
+
'.xml': 'application/xml',
|
|
664
653
|
};
|
|
665
|
-
const baseMimeType = mimeTypes[ext.toLowerCase()] ||
|
|
654
|
+
const baseMimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
666
655
|
// Add charset for text-based files
|
|
667
656
|
const textTypes = [
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
657
|
+
'text/',
|
|
658
|
+
'application/json',
|
|
659
|
+
'application/javascript',
|
|
660
|
+
'application/xml',
|
|
661
|
+
'image/svg+xml',
|
|
673
662
|
];
|
|
674
|
-
const needsCharset = textTypes.some(
|
|
675
|
-
const contentType = needsCharset
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
res.setHeader("Content-Type", contentType);
|
|
679
|
-
res.setHeader("Content-Length", stats.size);
|
|
663
|
+
const needsCharset = textTypes.some(type => baseMimeType.startsWith(type));
|
|
664
|
+
const contentType = needsCharset ? `${baseMimeType}; charset=utf-8` : baseMimeType;
|
|
665
|
+
res.setHeader('Content-Type', contentType);
|
|
666
|
+
res.setHeader('Content-Length', stats.size);
|
|
680
667
|
// Cache headers
|
|
681
668
|
if (options.maxAge) {
|
|
682
|
-
res.setHeader(
|
|
669
|
+
res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
|
|
683
670
|
}
|
|
684
671
|
// ETag support
|
|
685
672
|
if (options.etag !== false) {
|
|
686
673
|
const etag = crypto
|
|
687
|
-
.createHash(
|
|
674
|
+
.createHash('md5')
|
|
688
675
|
.update(`${stats.mtime.getTime()}-${stats.size}`)
|
|
689
|
-
.digest(
|
|
690
|
-
res.setHeader(
|
|
676
|
+
.digest('hex');
|
|
677
|
+
res.setHeader('ETag', `"${etag}"`);
|
|
691
678
|
// Handle conditional requests
|
|
692
|
-
const ifNoneMatch = req.headers[
|
|
679
|
+
const ifNoneMatch = req.headers['if-none-match'];
|
|
693
680
|
if (ifNoneMatch === `"${etag}"`) {
|
|
694
681
|
res.statusCode = 304;
|
|
695
682
|
res.end();
|
|
@@ -697,7 +684,7 @@ exports.middleware = {
|
|
|
697
684
|
}
|
|
698
685
|
}
|
|
699
686
|
// Handle HEAD requests
|
|
700
|
-
if (req.method ===
|
|
687
|
+
if (req.method === 'HEAD') {
|
|
701
688
|
res.end();
|
|
702
689
|
return;
|
|
703
690
|
}
|
|
@@ -706,16 +693,14 @@ exports.middleware = {
|
|
|
706
693
|
res.end(data);
|
|
707
694
|
}
|
|
708
695
|
catch (error) {
|
|
709
|
-
res
|
|
710
|
-
.status(500)
|
|
711
|
-
.json({ success: false, error: "Internal server error" });
|
|
696
|
+
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
712
697
|
}
|
|
713
698
|
};
|
|
714
699
|
},
|
|
715
700
|
upload: (options = {}) => {
|
|
716
701
|
return (req, res, next) => {
|
|
717
|
-
const contentType = req.headers[
|
|
718
|
-
if (!contentType.includes(
|
|
702
|
+
const contentType = req.headers['content-type'] || '';
|
|
703
|
+
if (!contentType.includes('multipart/form-data')) {
|
|
719
704
|
next();
|
|
720
705
|
return;
|
|
721
706
|
}
|
|
@@ -766,8 +751,8 @@ exports.middleware = {
|
|
|
766
751
|
// Add render method to response
|
|
767
752
|
res.render = async (template, data = {}) => {
|
|
768
753
|
try {
|
|
769
|
-
const fs = await Promise.resolve().then(() => __importStar(require(
|
|
770
|
-
const path = await Promise.resolve().then(() => __importStar(require(
|
|
754
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
755
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
771
756
|
const templatePath = path.join(options.views, `${template}.html`);
|
|
772
757
|
let templateContent;
|
|
773
758
|
// Check cache first
|
|
@@ -775,7 +760,7 @@ exports.middleware = {
|
|
|
775
760
|
templateContent = templateCache.get(templatePath);
|
|
776
761
|
}
|
|
777
762
|
else {
|
|
778
|
-
templateContent = await fs.readFile(templatePath,
|
|
763
|
+
templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
779
764
|
if (options.cache) {
|
|
780
765
|
templateCache.set(templatePath, templateContent);
|
|
781
766
|
}
|
|
@@ -788,44 +773,40 @@ exports.middleware = {
|
|
|
788
773
|
});
|
|
789
774
|
// Handle nested object properties like {{user.name}}
|
|
790
775
|
rendered = rendered.replace(/\{\{([\w.]+)\}\}/g, (match, key) => {
|
|
791
|
-
const value = key
|
|
792
|
-
.split(".")
|
|
793
|
-
.reduce((obj, prop) => obj?.[prop], data);
|
|
776
|
+
const value = key.split('.').reduce((obj, prop) => obj?.[prop], data);
|
|
794
777
|
return value !== undefined ? String(value) : match;
|
|
795
778
|
});
|
|
796
779
|
// Handle loops: {{#each items}}{{name}}{{/each}}
|
|
797
780
|
rendered = rendered.replace(/\{\{#each (\w+)\}\}(.*?)\{\{\/each\}\}/gs, (match, arrayKey, template) => {
|
|
798
781
|
const array = data[arrayKey];
|
|
799
782
|
if (!Array.isArray(array))
|
|
800
|
-
return
|
|
783
|
+
return '';
|
|
801
784
|
return array
|
|
802
|
-
.map(
|
|
785
|
+
.map(item => {
|
|
803
786
|
let itemTemplate = template;
|
|
804
787
|
// Replace variables in the loop template
|
|
805
788
|
itemTemplate = itemTemplate.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
806
|
-
return item[key] !== undefined
|
|
807
|
-
? String(item[key])
|
|
808
|
-
: match;
|
|
789
|
+
return item[key] !== undefined ? String(item[key]) : match;
|
|
809
790
|
});
|
|
810
791
|
return itemTemplate;
|
|
811
792
|
})
|
|
812
|
-
.join(
|
|
793
|
+
.join('');
|
|
813
794
|
});
|
|
814
795
|
// Handle conditionals: {{#if condition}}content{{/if}}
|
|
815
796
|
rendered = rendered.replace(/\{\{#if (\w+)\}\}(.*?)\{\{\/if\}\}/gs, (match, conditionKey, content) => {
|
|
816
797
|
const condition = data[conditionKey];
|
|
817
|
-
return condition ? content :
|
|
798
|
+
return condition ? content : '';
|
|
818
799
|
});
|
|
819
800
|
// Handle layout
|
|
820
801
|
if (options.defaultLayout) {
|
|
821
|
-
const layoutPath = path.join(options.views,
|
|
802
|
+
const layoutPath = path.join(options.views, 'layouts', `${options.defaultLayout}.html`);
|
|
822
803
|
try {
|
|
823
804
|
let layoutContent;
|
|
824
805
|
if (options.cache && templateCache.has(layoutPath)) {
|
|
825
806
|
layoutContent = templateCache.get(layoutPath);
|
|
826
807
|
}
|
|
827
808
|
else {
|
|
828
|
-
layoutContent = await fs.readFile(layoutPath,
|
|
809
|
+
layoutContent = await fs.readFile(layoutPath, 'utf-8');
|
|
829
810
|
if (options.cache) {
|
|
830
811
|
templateCache.set(layoutPath, layoutContent);
|
|
831
812
|
}
|
|
@@ -836,13 +817,11 @@ exports.middleware = {
|
|
|
836
817
|
// Layout not found, use template as-is
|
|
837
818
|
}
|
|
838
819
|
}
|
|
839
|
-
res.setHeader(
|
|
820
|
+
res.setHeader('Content-Type', 'text/html');
|
|
840
821
|
res.end(rendered);
|
|
841
822
|
}
|
|
842
823
|
catch (error) {
|
|
843
|
-
res
|
|
844
|
-
.status(500)
|
|
845
|
-
.json({ success: false, error: "Template rendering failed" });
|
|
824
|
+
res.status(500).json({ success: false, error: 'Template rendering failed' });
|
|
846
825
|
}
|
|
847
826
|
};
|
|
848
827
|
next();
|
|
@@ -854,13 +833,11 @@ exports.middleware = {
|
|
|
854
833
|
// Add HTTP/2 push capability to response
|
|
855
834
|
res.push = (path, options = {}) => {
|
|
856
835
|
// Check if HTTP/2 is supported
|
|
857
|
-
if (req.httpVersion ===
|
|
858
|
-
res.stream &&
|
|
859
|
-
res.stream.pushAllowed) {
|
|
836
|
+
if (req.httpVersion === '2.0' && res.stream && res.stream.pushAllowed) {
|
|
860
837
|
try {
|
|
861
838
|
const pushStream = res.stream.pushStream({
|
|
862
|
-
|
|
863
|
-
|
|
839
|
+
':method': 'GET',
|
|
840
|
+
':path': path,
|
|
864
841
|
...options.headers,
|
|
865
842
|
});
|
|
866
843
|
if (pushStream) {
|
|
@@ -879,7 +856,7 @@ exports.middleware = {
|
|
|
879
856
|
for (const resource of options.resources) {
|
|
880
857
|
res.push?.(resource.path, {
|
|
881
858
|
headers: {
|
|
882
|
-
|
|
859
|
+
'content-type': resource.type || 'text/plain',
|
|
883
860
|
},
|
|
884
861
|
});
|
|
885
862
|
}
|
|
@@ -891,16 +868,14 @@ exports.middleware = {
|
|
|
891
868
|
sse: (options = {}) => {
|
|
892
869
|
return (req, res, next) => {
|
|
893
870
|
// Only handle SSE requests
|
|
894
|
-
if (req.headers.accept?.includes(
|
|
871
|
+
if (req.headers.accept?.includes('text/event-stream')) {
|
|
895
872
|
// Set SSE headers
|
|
896
873
|
res.writeHead(200, {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
Connection:
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
? "Cache-Control"
|
|
903
|
-
: undefined,
|
|
874
|
+
'Content-Type': 'text/event-stream',
|
|
875
|
+
'Cache-Control': 'no-cache',
|
|
876
|
+
Connection: 'keep-alive',
|
|
877
|
+
'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
|
|
878
|
+
'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
|
|
904
879
|
});
|
|
905
880
|
// Add SSE methods to response
|
|
906
881
|
res.sendEvent = (data, event, id) => {
|
|
@@ -908,7 +883,7 @@ exports.middleware = {
|
|
|
908
883
|
res.write(`id: ${id}\n`);
|
|
909
884
|
if (event)
|
|
910
885
|
res.write(`event: ${event}\n`);
|
|
911
|
-
res.write(`data: ${typeof data ===
|
|
886
|
+
res.write(`data: ${typeof data === 'string' ? data : JSON.stringify(data)}\n\n`);
|
|
912
887
|
};
|
|
913
888
|
res.sendComment = (comment) => {
|
|
914
889
|
res.write(`: ${comment}\n\n`);
|
|
@@ -920,7 +895,7 @@ exports.middleware = {
|
|
|
920
895
|
let heartbeatInterval = null;
|
|
921
896
|
if (options.heartbeat) {
|
|
922
897
|
heartbeatInterval = setInterval(() => {
|
|
923
|
-
res.sendComment(
|
|
898
|
+
res.sendComment('heartbeat');
|
|
924
899
|
}, options.heartbeat);
|
|
925
900
|
}
|
|
926
901
|
// Set retry if configured
|
|
@@ -928,7 +903,7 @@ exports.middleware = {
|
|
|
928
903
|
res.sendRetry(options.retry);
|
|
929
904
|
}
|
|
930
905
|
// Clean up on close
|
|
931
|
-
req.on(
|
|
906
|
+
req.on('close', () => {
|
|
932
907
|
if (heartbeatInterval) {
|
|
933
908
|
clearInterval(heartbeatInterval);
|
|
934
909
|
}
|
|
@@ -945,28 +920,28 @@ exports.middleware = {
|
|
|
945
920
|
// Add range support to response
|
|
946
921
|
res.sendRange = async (filePath, stats) => {
|
|
947
922
|
try {
|
|
948
|
-
const fs = await Promise.resolve().then(() => __importStar(require(
|
|
949
|
-
const path = await Promise.resolve().then(() => __importStar(require(
|
|
923
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
924
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
950
925
|
if (!stats) {
|
|
951
926
|
stats = await fs.stat(filePath);
|
|
952
927
|
}
|
|
953
928
|
const fileSize = stats.size;
|
|
954
929
|
const range = req.headers.range;
|
|
955
930
|
// Set Accept-Ranges header
|
|
956
|
-
res.setHeader(
|
|
931
|
+
res.setHeader('Accept-Ranges', options.acceptRanges || 'bytes');
|
|
957
932
|
if (!range) {
|
|
958
933
|
// No range requested, send entire file
|
|
959
|
-
res.setHeader(
|
|
934
|
+
res.setHeader('Content-Length', fileSize);
|
|
960
935
|
const data = await fs.readFile(filePath);
|
|
961
936
|
res.end(data);
|
|
962
937
|
return;
|
|
963
938
|
}
|
|
964
939
|
// Parse range header
|
|
965
940
|
const ranges = range
|
|
966
|
-
.replace(/bytes=/,
|
|
967
|
-
.split(
|
|
968
|
-
.map(
|
|
969
|
-
const [start, end] = r.split(
|
|
941
|
+
.replace(/bytes=/, '')
|
|
942
|
+
.split(',')
|
|
943
|
+
.map(r => {
|
|
944
|
+
const [start, end] = r.split('-');
|
|
970
945
|
return {
|
|
971
946
|
start: start ? parseInt(start) : 0,
|
|
972
947
|
end: end ? parseInt(end) : fileSize - 1,
|
|
@@ -974,7 +949,7 @@ exports.middleware = {
|
|
|
974
949
|
});
|
|
975
950
|
// Validate ranges
|
|
976
951
|
if (options.maxRanges && ranges.length > options.maxRanges) {
|
|
977
|
-
res.status(416).json({ success: false, error:
|
|
952
|
+
res.status(416).json({ success: false, error: 'Too many ranges' });
|
|
978
953
|
return;
|
|
979
954
|
}
|
|
980
955
|
if (ranges.length === 1) {
|
|
@@ -982,15 +957,15 @@ exports.middleware = {
|
|
|
982
957
|
const { start, end } = ranges[0];
|
|
983
958
|
const chunkSize = end - start + 1;
|
|
984
959
|
if (start >= fileSize || end >= fileSize) {
|
|
985
|
-
res.status(416).setHeader(
|
|
986
|
-
res.json({ success: false, error:
|
|
960
|
+
res.status(416).setHeader('Content-Range', `bytes */${fileSize}`);
|
|
961
|
+
res.json({ success: false, error: 'Range not satisfiable' });
|
|
987
962
|
return;
|
|
988
963
|
}
|
|
989
964
|
res.status(206);
|
|
990
|
-
res.setHeader(
|
|
991
|
-
res.setHeader(
|
|
965
|
+
res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
|
|
966
|
+
res.setHeader('Content-Length', chunkSize);
|
|
992
967
|
// Stream the range
|
|
993
|
-
const stream = require(
|
|
968
|
+
const stream = require('fs').createReadStream(filePath, {
|
|
994
969
|
start,
|
|
995
970
|
end,
|
|
996
971
|
});
|
|
@@ -998,21 +973,21 @@ exports.middleware = {
|
|
|
998
973
|
}
|
|
999
974
|
else {
|
|
1000
975
|
// Multiple ranges - multipart response
|
|
1001
|
-
const boundary =
|
|
976
|
+
const boundary = 'MULTIPART_BYTERANGES';
|
|
1002
977
|
res.status(206);
|
|
1003
|
-
res.setHeader(
|
|
978
|
+
res.setHeader('Content-Type', `multipart/byteranges; boundary=${boundary}`);
|
|
1004
979
|
for (const { start, end } of ranges) {
|
|
1005
980
|
if (start >= fileSize || end >= fileSize)
|
|
1006
981
|
continue;
|
|
1007
982
|
const chunkSize = end - start + 1;
|
|
1008
983
|
res.write(`\r\n--${boundary}\r\n`);
|
|
1009
984
|
res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
|
|
1010
|
-
const stream = require(
|
|
985
|
+
const stream = require('fs').createReadStream(filePath, {
|
|
1011
986
|
start,
|
|
1012
987
|
end,
|
|
1013
988
|
});
|
|
1014
|
-
await new Promise(
|
|
1015
|
-
stream.on(
|
|
989
|
+
await new Promise(resolve => {
|
|
990
|
+
stream.on('end', resolve);
|
|
1016
991
|
stream.pipe(res, { end: false });
|
|
1017
992
|
});
|
|
1018
993
|
}
|
|
@@ -1021,9 +996,7 @@ exports.middleware = {
|
|
|
1021
996
|
}
|
|
1022
997
|
}
|
|
1023
998
|
catch (error) {
|
|
1024
|
-
res
|
|
1025
|
-
.status(500)
|
|
1026
|
-
.json({ success: false, error: "Range request failed" });
|
|
999
|
+
res.status(500).json({ success: false, error: 'Range request failed' });
|
|
1027
1000
|
}
|
|
1028
1001
|
};
|
|
1029
1002
|
next();
|
|
@@ -1031,14 +1004,14 @@ exports.middleware = {
|
|
|
1031
1004
|
},
|
|
1032
1005
|
// CSRF Protection middleware
|
|
1033
1006
|
csrf: (options = {}) => {
|
|
1034
|
-
const secret = options.secret ||
|
|
1007
|
+
const secret = options.secret || 'moro-csrf-secret';
|
|
1035
1008
|
const tokenLength = options.tokenLength || 32;
|
|
1036
|
-
const cookieName = options.cookieName ||
|
|
1037
|
-
const headerName = options.headerName ||
|
|
1038
|
-
const ignoreMethods = options.ignoreMethods || [
|
|
1009
|
+
const cookieName = options.cookieName || '_csrf';
|
|
1010
|
+
const headerName = options.headerName || 'x-csrf-token';
|
|
1011
|
+
const ignoreMethods = options.ignoreMethods || ['GET', 'HEAD', 'OPTIONS'];
|
|
1039
1012
|
const generateToken = () => {
|
|
1040
|
-
const crypto = require(
|
|
1041
|
-
return crypto.randomBytes(tokenLength).toString(
|
|
1013
|
+
const crypto = require('crypto');
|
|
1014
|
+
return crypto.randomBytes(tokenLength).toString('hex');
|
|
1042
1015
|
};
|
|
1043
1016
|
const verifyToken = (token, sessionToken) => {
|
|
1044
1017
|
return token && sessionToken && token === sessionToken;
|
|
@@ -1051,9 +1024,8 @@ exports.middleware = {
|
|
|
1051
1024
|
// Set token in cookie
|
|
1052
1025
|
res.cookie(cookieName, req._csrfToken, {
|
|
1053
1026
|
httpOnly: true,
|
|
1054
|
-
sameSite: options.sameSite !== false ?
|
|
1055
|
-
secure: req.headers[
|
|
1056
|
-
req.socket.encrypted,
|
|
1027
|
+
sameSite: options.sameSite !== false ? 'strict' : undefined,
|
|
1028
|
+
secure: req.headers['x-forwarded-proto'] === 'https' || req.socket.encrypted,
|
|
1057
1029
|
});
|
|
1058
1030
|
}
|
|
1059
1031
|
return req._csrfToken;
|
|
@@ -1064,16 +1036,14 @@ exports.middleware = {
|
|
|
1064
1036
|
return;
|
|
1065
1037
|
}
|
|
1066
1038
|
// Get token from header or body
|
|
1067
|
-
const token = req.headers[headerName] ||
|
|
1068
|
-
(req.body && req.body._csrf) ||
|
|
1069
|
-
(req.query && req.query._csrf);
|
|
1039
|
+
const token = req.headers[headerName] || (req.body && req.body._csrf) || (req.query && req.query._csrf);
|
|
1070
1040
|
// Get session token from cookie
|
|
1071
1041
|
const sessionToken = req.cookies?.[cookieName];
|
|
1072
|
-
if (!verifyToken(token, sessionToken ||
|
|
1042
|
+
if (!verifyToken(token, sessionToken || '')) {
|
|
1073
1043
|
res.status(403).json({
|
|
1074
1044
|
success: false,
|
|
1075
|
-
error:
|
|
1076
|
-
code:
|
|
1045
|
+
error: 'Invalid CSRF token',
|
|
1046
|
+
code: 'CSRF_TOKEN_MISMATCH',
|
|
1077
1047
|
});
|
|
1078
1048
|
return;
|
|
1079
1049
|
}
|
|
@@ -1087,7 +1057,7 @@ exports.middleware = {
|
|
|
1087
1057
|
defaultSrc: ["'self'"],
|
|
1088
1058
|
scriptSrc: ["'self'"],
|
|
1089
1059
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1090
|
-
imgSrc: ["'self'",
|
|
1060
|
+
imgSrc: ["'self'", 'data:', 'https:'],
|
|
1091
1061
|
connectSrc: ["'self'"],
|
|
1092
1062
|
fontSrc: ["'self'"],
|
|
1093
1063
|
objectSrc: ["'none'"],
|
|
@@ -1097,30 +1067,27 @@ exports.middleware = {
|
|
|
1097
1067
|
// Generate nonce if requested
|
|
1098
1068
|
let nonce;
|
|
1099
1069
|
if (options.nonce) {
|
|
1100
|
-
const crypto = require(
|
|
1101
|
-
nonce = crypto.randomBytes(16).toString(
|
|
1070
|
+
const crypto = require('crypto');
|
|
1071
|
+
nonce = crypto.randomBytes(16).toString('base64');
|
|
1102
1072
|
req.cspNonce = nonce;
|
|
1103
1073
|
}
|
|
1104
1074
|
// Build CSP header value
|
|
1105
1075
|
const cspParts = [];
|
|
1106
1076
|
for (const [directive, sources] of Object.entries(directives)) {
|
|
1107
|
-
if (directive ===
|
|
1108
|
-
cspParts.push(
|
|
1077
|
+
if (directive === 'upgradeInsecureRequests' && sources === true) {
|
|
1078
|
+
cspParts.push('upgrade-insecure-requests');
|
|
1109
1079
|
}
|
|
1110
|
-
else if (directive ===
|
|
1111
|
-
cspParts.push(
|
|
1080
|
+
else if (directive === 'blockAllMixedContent' && sources === true) {
|
|
1081
|
+
cspParts.push('block-all-mixed-content');
|
|
1112
1082
|
}
|
|
1113
1083
|
else if (Array.isArray(sources)) {
|
|
1114
|
-
let sourceList = sources.join(
|
|
1084
|
+
let sourceList = sources.join(' ');
|
|
1115
1085
|
// Add nonce to script-src and style-src if enabled
|
|
1116
|
-
if (nonce &&
|
|
1117
|
-
(directive === "scriptSrc" || directive === "styleSrc")) {
|
|
1086
|
+
if (nonce && (directive === 'scriptSrc' || directive === 'styleSrc')) {
|
|
1118
1087
|
sourceList += ` 'nonce-${nonce}'`;
|
|
1119
1088
|
}
|
|
1120
1089
|
// Convert camelCase to kebab-case
|
|
1121
|
-
const kebabDirective = directive
|
|
1122
|
-
.replace(/([A-Z])/g, "-$1")
|
|
1123
|
-
.toLowerCase();
|
|
1090
|
+
const kebabDirective = directive.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
1124
1091
|
cspParts.push(`${kebabDirective} ${sourceList}`);
|
|
1125
1092
|
}
|
|
1126
1093
|
}
|
|
@@ -1128,10 +1095,10 @@ exports.middleware = {
|
|
|
1128
1095
|
if (options.reportUri) {
|
|
1129
1096
|
cspParts.push(`report-uri ${options.reportUri}`);
|
|
1130
1097
|
}
|
|
1131
|
-
const cspValue = cspParts.join(
|
|
1098
|
+
const cspValue = cspParts.join('; ');
|
|
1132
1099
|
const headerName = options.reportOnly
|
|
1133
|
-
?
|
|
1134
|
-
:
|
|
1100
|
+
? 'Content-Security-Policy-Report-Only'
|
|
1101
|
+
: 'Content-Security-Policy';
|
|
1135
1102
|
res.setHeader(headerName, cspValue);
|
|
1136
1103
|
next();
|
|
1137
1104
|
};
|
|
@@ -1148,7 +1115,7 @@ function parseSize(size) {
|
|
|
1148
1115
|
if (!match)
|
|
1149
1116
|
return 1024 * 1024; // Default 1MB
|
|
1150
1117
|
const value = parseFloat(match[1]);
|
|
1151
|
-
const unit = match[2] ||
|
|
1118
|
+
const unit = match[2] || 'b';
|
|
1152
1119
|
return Math.round(value * units[unit]);
|
|
1153
1120
|
}
|
|
1154
1121
|
//# sourceMappingURL=http-server.js.map
|