corecdtl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +202 -0
  3. package/build/Release/hypernode.node +0 -0
  4. package/dist/http/chunker/ChunkParser.d.ts +8 -0
  5. package/dist/http/chunker/ChunkParser.js +2 -0
  6. package/dist/http/chunker/ChunkProgression.d.ts +25 -0
  7. package/dist/http/chunker/ChunkProgression.js +55 -0
  8. package/dist/http/chunker/FixedChunkedParser.d.ts +14 -0
  9. package/dist/http/chunker/FixedChunkedParser.js +49 -0
  10. package/dist/http/chunker/StreamingChunkedParser.d.ts +19 -0
  11. package/dist/http/chunker/StreamingChunkedParser.js +94 -0
  12. package/dist/http/chunker/UntilEndChunkerParser.d.ts +37 -0
  13. package/dist/http/chunker/UntilEndChunkerParser.js +64 -0
  14. package/dist/http/content/encoding.d.ts +3 -0
  15. package/dist/http/content/encoding.js +38 -0
  16. package/dist/http/content/parser.d.ts +1 -0
  17. package/dist/http/content/parser.js +64 -0
  18. package/dist/http/context/ApiContext.d.ts +14 -0
  19. package/dist/http/context/ApiContext.js +151 -0
  20. package/dist/http/context/HttpContext.d.ts +42 -0
  21. package/dist/http/context/HttpContext.js +231 -0
  22. package/dist/http/context/WebContext.d.ts +31 -0
  23. package/dist/http/context/WebContext.js +320 -0
  24. package/dist/http/factory/accumulator.d.ts +13 -0
  25. package/dist/http/factory/accumulator.js +221 -0
  26. package/dist/http/factory/factory.d.ts +5 -0
  27. package/dist/http/factory/factory.js +97 -0
  28. package/dist/http/factory/pipeline.d.ts +3 -0
  29. package/dist/http/factory/pipeline.js +102 -0
  30. package/dist/http/response/HttpResponseBase.d.ts +10 -0
  31. package/dist/http/response/HttpResponseBase.js +2 -0
  32. package/dist/http/response/PipeResponseBase.d.ts +31 -0
  33. package/dist/http/response/PipeResponseBase.js +115 -0
  34. package/dist/http.d.ts +567 -0
  35. package/dist/http.js +59 -0
  36. package/dist/hypernode.d.ts +31 -0
  37. package/dist/hypernode.js +8 -0
  38. package/dist/index.d.ts +9 -0
  39. package/dist/index.js +39 -0
  40. package/package.json +63 -0
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const parser_1 = require("../content/parser");
40
+ const encoding_1 = require("../content/encoding");
41
+ const http_1 = require("../../http");
42
+ const HttpContext_1 = __importDefault(require("./HttpContext"));
43
+ const Factory = __importStar(require("../factory/factory"));
44
+ const net_1 = __importDefault(require("net"));
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const path_1 = __importDefault(require("path"));
47
+ const hypernode_1 = require("../../hypernode");
48
+ function isFingerprinted(name) {
49
+ return /\.[a-f0-9]{8,}\./i.test(name);
50
+ }
51
+ const MIME_MAP = {
52
+ ".js": "application/javascript",
53
+ ".mjs": "application/javascript",
54
+ ".css": "text/css",
55
+ ".html": "text/html",
56
+ ".json": "application/json",
57
+ ".png": "image/png",
58
+ ".jpg": "image/jpeg",
59
+ ".jpeg": "image/jpeg",
60
+ ".gif": "image/gif",
61
+ ".svg": "image/svg+xml",
62
+ ".webp": "image/webp",
63
+ ".woff": "font/woff",
64
+ ".woff2": "font/woff2"
65
+ };
66
+ class WebContext extends HttpContext_1.default {
67
+ constructor(ctxOpts, opts) {
68
+ super(opts);
69
+ this.contentDecoding = encoding_1.contentDecodingTable;
70
+ this.contentEncoding = encoding_1.contentEncodingTable;
71
+ this.contentTypeParsers = parser_1.contentParserTable;
72
+ this.parseInitial = (socket, chunk, p) => {
73
+ const routeId = this.httpCore.scannerRouteFirst(chunk, p, this.state.maxHeaderNameSize, this.state.maxHeaderValueSize, this.state.maxContentSize, this.state.requestQuerySize);
74
+ if (p.retFlag !== http_1.Http.RetFlagBits.FLAG_OK) {
75
+ switch (p.retFlag) {
76
+ // --- CORS 204 ---
77
+ case http_1.Http.RetFlagBits.FLAG_CORS_PREFLIGHT:
78
+ socket.write(Buffer.from("HTTP/1.1 204 No Content\r\n" +
79
+ this.state.corsHeaders + "\r\n" +
80
+ "Content-Length: 0\r\n\r\n"));
81
+ socket.destroy();
82
+ return;
83
+ // --- VERSION UNSUPPORTED ---
84
+ case http_1.Http.RetFlagBits.FLAG_HTTP_VERSION_UNSUPPORTED:
85
+ socket.write(this.errorRespMap.RESP_505);
86
+ socket.destroy();
87
+ return;
88
+ // --- METHOD NOT ALLOWED ---
89
+ case http_1.Http.RetFlagBits.FLAG_METHOD_NOT_ALLOWED:
90
+ socket.write(this.errorRespMap.RESP_405);
91
+ socket.destroy();
92
+ return;
93
+ // --- REQUEST QUERY EXCEEDED ---
94
+ case http_1.Http.RetFlagBits.FLAG_REQUEST_QUERY_EXCEEDED:
95
+ socket.write(this.errorRespMap.RESP_414);
96
+ socket.destroySoon();
97
+ return;
98
+ // --- NOT FOUND ---
99
+ case http_1.Http.RetFlagBits.FLAG_NOT_FOUND:
100
+ if (this.enableCors) {
101
+ socket.write(this.errorRespMap.RESP_204);
102
+ }
103
+ else {
104
+ socket.write(this.errorRespMap.RESP_404);
105
+ }
106
+ socket.destroy();
107
+ return;
108
+ // === HEADER ERRORS ===
109
+ case http_1.Http.RetFlagBits.FLAG_INVALID_ARGUMENT:
110
+ case http_1.Http.RetFlagBits.FLAG_INVALID_HEADER:
111
+ case http_1.Http.RetFlagBits.FLAG_INVALID_CONTENT_LENGTH:
112
+ case http_1.Http.RetFlagBits.FLAG_CONTENT_LENGTH_EXCEEDED:
113
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_SIZE:
114
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_NAME_SIZE:
115
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_VALUE_SIZE:
116
+ case http_1.Http.RetFlagBits.FLAG_DUPLICATE_SINGLE_HEADER:
117
+ socket.write(this.errorRespMap.RESP_400);
118
+ socket.destroy();
119
+ return;
120
+ case http_1.Http.RetFlagBits.FLAG_UNTERMINATED_HEADERS:
121
+ p.rawBuf = chunk;
122
+ p.routePipe = this.routePipes[routeId];
123
+ p.fn = this.parseHeader;
124
+ return;
125
+ // --- OTHER (fallback) ---
126
+ default:
127
+ socket.write(this.errorRespMap.RESP_400);
128
+ socket.destroy();
129
+ return;
130
+ }
131
+ }
132
+ this.routeDefinationFns[routeId](socket, p, routeId, chunk);
133
+ };
134
+ this.publicRouteDefinationFn = (socket, p, routeId, chunk) => {
135
+ const assetPath = this.assetParser.handlePublicAsset(chunk, 4 + 1 // GET(3) 1 is SPACE and Last 1 is => /
136
+ );
137
+ let entry = this.assetCache.get(assetPath);
138
+ if (!entry) {
139
+ entry = this.loadAsset(assetPath);
140
+ if (!entry) {
141
+ socket.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n");
142
+ p.free();
143
+ socket.end();
144
+ return;
145
+ }
146
+ this.assetCache.set(assetPath, entry);
147
+ }
148
+ socket.write(entry.payload);
149
+ p.reset();
150
+ socket.end();
151
+ };
152
+ this.spaRouteDefinationFn = (socket, p, routeId, _) => {
153
+ p.free();
154
+ socket.write(this.spaRespBuffer);
155
+ socket.end();
156
+ };
157
+ this.dynamicRouteDefinationFn = (socket, p, routeId, chunk) => {
158
+ p.rawBuf = chunk;
159
+ const h = p.headers;
160
+ const hostHeader = h.host;
161
+ if (!hostHeader) {
162
+ socket.write(this.errorRespMap.RESP_400);
163
+ socket.destroy();
164
+ return;
165
+ }
166
+ p.routePipe = this.routePipes[routeId];
167
+ p.routePipe.accumulateHandler(socket, p);
168
+ };
169
+ this.parseHeader = (socket, chunk, p) => {
170
+ p.rawBuf = Buffer.concat([p.rawBuf, chunk]);
171
+ this.httpCore.scannerHeader(p.rawBuf, p, this.state.maxHeaderNameSize, this.state.maxHeaderValueSize, this.state.maxContentSize);
172
+ if (p.retFlag !== http_1.Http.RetFlagBits.FLAG_OK) {
173
+ switch (p.retFlag) {
174
+ case http_1.Http.RetFlagBits.FLAG_INVALID_ARGUMENT:
175
+ case http_1.Http.RetFlagBits.FLAG_INVALID_HEADER:
176
+ case http_1.Http.RetFlagBits.FLAG_INVALID_CONTENT_LENGTH:
177
+ case http_1.Http.RetFlagBits.FLAG_CONTENT_LENGTH_EXCEEDED:
178
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_SIZE:
179
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_NAME_SIZE:
180
+ case http_1.Http.RetFlagBits.FLAG_MAX_HEADER_VALUE_SIZE:
181
+ socket.write(this.errorRespMap.RESP_400);
182
+ socket.destroySoon();
183
+ return;
184
+ case http_1.Http.RetFlagBits.FLAG_UNTERMINATED_HEADERS:
185
+ return;
186
+ }
187
+ }
188
+ this.routeDefinationFns[p.routePipe.routeId](socket, p, p.routePipe.routeId, chunk);
189
+ };
190
+ this.publicRoutePathName = ctxOpts?.publicStaticPath == undefined ? "dist" : ctxOpts.publicStaticPath;
191
+ this.publicStaticRoute = ctxOpts?.publicStaticRoute == undefined ? "/public" : ctxOpts.publicStaticRoute;
192
+ this.assetParser = new hypernode_1.hypernode.PublicAssetParser();
193
+ this.assetParser.setAssetRoute(this.publicStaticRoute);
194
+ this.spaRootPath = ctxOpts?.spaRootPath == undefined ? "dist/index.html" : ctxOpts.spaRootPath;
195
+ const _data = fs_1.default.readFileSync(this.spaRootPath);
196
+ const __resp = Buffer.from("HTTP/1.1 200 OK\r\n" +
197
+ "Content-Type: text/html; charset=utf-8\r\n" +
198
+ "Content-Length: " + _data.length + "\r\n" +
199
+ "Cache-Control: no-cache\r\n" +
200
+ "\r\n", "ascii");
201
+ this.assetCache = new Map();
202
+ this.setAllAssets();
203
+ this.spaRespBuffer = Buffer.concat([__resp, _data]);
204
+ this.initRuntime();
205
+ this.bindServer(opts?.netServerOptions);
206
+ }
207
+ bindServer(netServerOptions) {
208
+ this.server = new net_1.default.Server(netServerOptions, (socket) => {
209
+ let p = this.chunkPool.allocate();
210
+ if (!p) {
211
+ socket.destroy();
212
+ return;
213
+ }
214
+ socket.setTimeout(this.state.timeout);
215
+ // socket.setNoDelay(true);
216
+ socket.on("data", chunk => {
217
+ p.fn(socket, chunk, p);
218
+ });
219
+ socket.on("timeout", () => {
220
+ socket.destroy();
221
+ });
222
+ socket.on("error", (err) => {
223
+ socket.destroy();
224
+ });
225
+ socket.on("close", () => {
226
+ p.free();
227
+ });
228
+ });
229
+ }
230
+ registerRouters(mainRoute) {
231
+ let _startRoutePath = "/";
232
+ if (mainRoute == undefined) {
233
+ mainRoute = Factory.createRoute(_startRoutePath);
234
+ }
235
+ else {
236
+ _startRoutePath = mainRoute.url;
237
+ /* Same time add dynamic EPs */
238
+ }
239
+ const __routePublic = this.makeRoutePublic();
240
+ const _routeSPA = this.makeRouteSPA();
241
+ mainRoute.addRoute(__routePublic);
242
+ mainRoute.addRoute(_routeSPA);
243
+ super.registerRouters(mainRoute);
244
+ this.setRouteDefinationFn(_startRoutePath);
245
+ }
246
+ makeRouteSPA() {
247
+ const spaRoute = Factory.createRoute("*");
248
+ spaRoute.addEndpoint(Factory.createEndpoint(http_1.Http.HttpMethod.GET, "", null));
249
+ return spaRoute;
250
+ }
251
+ makeRoutePublic() {
252
+ const _route = Factory.createRoute(this.publicStaticRoute);
253
+ const _ep = Factory.createEndpoint(http_1.Http.HttpMethod.GET, "/*", null);
254
+ _route.addEndpoint(_ep);
255
+ return _route;
256
+ }
257
+ setRouteDefinationFn(_startRoute) {
258
+ const _routeDefinationFns = Array(this.routePipes.length);
259
+ for (let i = 0; i < this.routePipes.length; i++) {
260
+ const e = this.routePipes[i];
261
+ if (e.url == _startRoute.concat("*")) {
262
+ _routeDefinationFns[i] = this.spaRouteDefinationFn;
263
+ }
264
+ else if (e.url == _startRoute.concat(this.publicStaticRoute).concat("/*")) {
265
+ _routeDefinationFns[i] = this.publicRouteDefinationFn;
266
+ }
267
+ else {
268
+ _routeDefinationFns[i] = this.dynamicRouteDefinationFn;
269
+ }
270
+ }
271
+ this.routeDefinationFns = _routeDefinationFns;
272
+ }
273
+ setAllAssets() {
274
+ const walk = (dir, baseUrl) => {
275
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
276
+ for (const entry of entries) {
277
+ const fullPath = path_1.default.join(dir, entry.name);
278
+ const urlPath = path_1.default.posix.join(baseUrl, entry.name);
279
+ if (entry.isDirectory()) {
280
+ walk(fullPath, urlPath);
281
+ continue;
282
+ }
283
+ if (!entry.isFile())
284
+ continue;
285
+ const asset = this.loadAsset(urlPath);
286
+ if (!asset)
287
+ continue;
288
+ this.assetCache.set(urlPath, asset);
289
+ }
290
+ };
291
+ walk(this.publicRoutePathName, "");
292
+ }
293
+ loadAsset(assetPath) {
294
+ try {
295
+ const fullPath = path_1.default.join(this.publicRoutePathName, assetPath);
296
+ const body = fs_1.default.readFileSync(fullPath);
297
+ const size = body.length;
298
+ const ext = path_1.default.extname(assetPath).toLowerCase();
299
+ const mime = MIME_MAP[ext] ?? "application/octet-stream";
300
+ const immutable = isFingerprinted(assetPath);
301
+ const cacheControl = immutable
302
+ ? "public, max-age=31536000, immutable"
303
+ : "public, max-age=0, must-revalidate";
304
+ const headers = Buffer.from("HTTP/1.1 200 OK\r\n" +
305
+ `Content-Type: ${mime}\r\n` +
306
+ `Content-Length: ${size}\r\n` +
307
+ `Cache-Control: ${cacheControl}\r\n` +
308
+ "\r\n");
309
+ const payload = Buffer.concat([headers, body]);
310
+ return { headers, body, size, payload };
311
+ }
312
+ catch {
313
+ return undefined;
314
+ }
315
+ }
316
+ setHttpCore() {
317
+ super.setHttpCore("web");
318
+ }
319
+ }
320
+ exports.default = WebContext;
@@ -0,0 +1,13 @@
1
+ import { Http } from "../../http";
2
+ import net from "net";
3
+ export declare function createAccumulators(ctx: {
4
+ contentTypeParsers: Http.ContentTypeParser;
5
+ contentDecoding: Http.ContentDecoding;
6
+ errorRespMap: Http.HttpStaticResponseMap;
7
+ }): {
8
+ accumulatorHeadGet: (socket: net.Socket, p: Http.ChunkProgression) => void;
9
+ decisionAccumulate: (socket: net.Socket, p: Http.ChunkProgression) => void;
10
+ accumulatorDefinedType: (socket: net.Socket, p: Http.ChunkProgression) => Promise<void>;
11
+ accumulatorDefinedEncode: (socket: net.Socket, p: Http.ChunkProgression) => Promise<void>;
12
+ accumulatorDefinedTypeEncode: (socket: net.Socket, p: Http.ChunkProgression) => Promise<void>;
13
+ };
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAccumulators = createAccumulators;
4
+ function createAccumulators(ctx) {
5
+ const { contentTypeParsers, contentDecoding, errorRespMap } = ctx;
6
+ function accumulatorHeadGet(socket, p) {
7
+ socket.pause();
8
+ p.routePipe.pipeHandler(p, p.routePipe.mws, (ret) => {
9
+ socket.write(ret);
10
+ const con = p.headers.connection;
11
+ if (con == "close")
12
+ socket.destroySoon();
13
+ else {
14
+ p.reset();
15
+ socket.resume();
16
+ }
17
+ });
18
+ }
19
+ // ==============================
20
+ // UNTIL-END MODE
21
+ // ==============================
22
+ function accumulateUntilEnd(socket, chunk, p) {
23
+ if (p.rawBuf.length + chunk.length > p.routePipe.maxContentSize) {
24
+ socket.write(errorRespMap.RESP_413);
25
+ socket.destroy();
26
+ return;
27
+ }
28
+ p.chunkParser.untilEnd.write(chunk);
29
+ }
30
+ // ==============================
31
+ // FIXED LENGTH MODE
32
+ // ==============================
33
+ async function accumulateDef(socket, chunk, p) {
34
+ const acc = p.chunkParser.fixed;
35
+ const progress = acc.getTotalWrittenSize() + chunk.length;
36
+ if (progress > p.contentLen) {
37
+ socket.write(errorRespMap.RESP_400);
38
+ socket.destroy();
39
+ return;
40
+ }
41
+ if (progress > p.routePipe.maxContentSize) {
42
+ socket.write(errorRespMap.RESP_413);
43
+ socket.destroy();
44
+ return;
45
+ }
46
+ acc.write(chunk);
47
+ // BODY DONE
48
+ if (progress === p.contentLen) {
49
+ socket.pause();
50
+ const b = acc.getBody();
51
+ acc.free();
52
+ const ret = await p.routePipe.pipeHandler(b, p, contentTypeParsers, contentDecoding, p.routePipe.mws, (ret) => {
53
+ socket.write(ret);
54
+ const con = p.headers.Connection;
55
+ if (con == "close")
56
+ socket.destroySoon();
57
+ else {
58
+ p.reset();
59
+ socket.resume();
60
+ }
61
+ });
62
+ }
63
+ }
64
+ // ==============================
65
+ // CHUNKED MODE (Sync handler, async final processing)
66
+ // ==============================
67
+ function accumulateChunked(socket, chunk, p) {
68
+ const total = p.chunkParser.streaming.getTotalSize();
69
+ // @ts-ignore
70
+ if (total + chunk.length > p.routePipe.maxContentSize) {
71
+ socket.write(errorRespMap.RESP_400);
72
+ socket.end();
73
+ return;
74
+ }
75
+ p.chunkParser.streaming.write(chunk);
76
+ if (!p.chunkParser.streaming.isFinished()) {
77
+ return;
78
+ }
79
+ socket.pause();
80
+ const b = p.chunkParser.streaming.getBody();
81
+ p.chunkParser.streaming.free();
82
+ p.routePipe.pipeHandler(b, p, contentTypeParsers, contentDecoding, p.routePipe.mws, (ret) => {
83
+ socket.write(ret);
84
+ const con = p.headers.connection;
85
+ if (con == "close")
86
+ socket.destroySoon();
87
+ else {
88
+ p.reset();
89
+ socket.resume();
90
+ }
91
+ });
92
+ }
93
+ // ==============================
94
+ // DESICION ACCUMULATE AFTER HEADERS
95
+ // ==============================
96
+ function decisionAccumulate(socket, p) {
97
+ const h = p.headers;
98
+ const transferEnc = h["transfer-encoding"];
99
+ const contentLenStr = h["content-length"];
100
+ // ───────────────────────────────────────────────
101
+ // 1) CHUNKED MODE (Transfer-Encoding: chunked)
102
+ // ───────────────────────────────────────────────
103
+ if (transferEnc === "chunked") {
104
+ const already = p.rawBuf.slice(p.mainOffset);
105
+ p.fn = accumulateChunked;
106
+ // İlk chunk'ı senkron olarak işle
107
+ accumulateChunked(socket, already, p);
108
+ return;
109
+ }
110
+ // ───────────────────────────────────────────────
111
+ // 2) UNTIL_END MODE (no content-length)
112
+ // ───────────────────────────────────────────────
113
+ if (!contentLenStr) {
114
+ if (!p.routePipe.untilEnd) {
115
+ socket.write(errorRespMap.RESP_400);
116
+ socket.destroySoon();
117
+ return;
118
+ }
119
+ socket.on("end", () => {
120
+ const b = p.chunkParser.untilEnd.getBody();
121
+ p.routePipe.pipeHandler(b, p, contentTypeParsers, contentDecoding, p.routePipe.mws, () => {
122
+ p.chunkParser.untilEnd.free();
123
+ socket.destroy();
124
+ return;
125
+ });
126
+ });
127
+ p.fn = accumulateUntilEnd;
128
+ return;
129
+ }
130
+ // ───────────────────────────────────────────────
131
+ // 3) FIXED MODE (content-length N)
132
+ // ───────────────────────────────────────────────
133
+ p.contentLen = parseInt(contentLenStr);
134
+ // Empty body
135
+ if (p.contentLen === 0) {
136
+ socket.pause();
137
+ p.routePipe.pipeHandler(null, p, contentTypeParsers, contentDecoding, p.routePipe.mws, (ret) => {
138
+ socket.write(ret);
139
+ const con = h.connection;
140
+ if (con == "close")
141
+ socket.destroySoon();
142
+ else {
143
+ p.reset();
144
+ socket.resume();
145
+ }
146
+ });
147
+ return;
148
+ }
149
+ const already = p.rawBuf.slice(p.mainOffset);
150
+ // ───────────────────────────────────────────────
151
+ // exact match (body fully arrived)
152
+ // ───────────────────────────────────────────────
153
+ if (already.length === p.contentLen) {
154
+ // socket.pause();
155
+ p.routePipe.pipeHandler(already, p, contentTypeParsers, contentDecoding, p.routePipe.mws, (ret) => {
156
+ socket.write(ret);
157
+ if (h.connection == "close") {
158
+ socket.destroySoon();
159
+ }
160
+ else {
161
+ p.reset();
162
+ socket.resume();
163
+ }
164
+ });
165
+ return;
166
+ }
167
+ // ───────────────────────────────────────────────
168
+ // overflow attempt → reject immediately
169
+ // ───────────────────────────────────────────────
170
+ if (already.length > p.contentLen) {
171
+ socket.write(errorRespMap.RESP_400);
172
+ socket.destroySoon();
173
+ return;
174
+ }
175
+ // ───────────────────────────────────────────────
176
+ // incomplete → use FIXED accumulator
177
+ // ───────────────────────────────────────────────
178
+ p.chunkParser.fixed.allocateBuffer(p.contentLen);
179
+ p.chunkParser.fixed.write(already);
180
+ p.fn = accumulateDef;
181
+ }
182
+ async function accumulatorDefinedType(socket, p) {
183
+ const h = p.headers;
184
+ if (p.routePipe.ct.type !== h["content-type"]) {
185
+ socket.write(errorRespMap.RESP_400);
186
+ socket.destroy();
187
+ return;
188
+ }
189
+ await decisionAccumulate(socket, p);
190
+ }
191
+ async function accumulatorDefinedEncode(socket, p) {
192
+ const h = p.headers;
193
+ if (p.routePipe.ct.encoding !== h["content-encoding"]) {
194
+ socket.write(errorRespMap.RESP_400);
195
+ socket.destroy();
196
+ return;
197
+ }
198
+ await decisionAccumulate(socket, p);
199
+ }
200
+ async function accumulatorDefinedTypeEncode(socket, p) {
201
+ const h = p.headers;
202
+ if (p.routePipe.ct.type !== h["content-type"]) {
203
+ socket.write(errorRespMap.RESP_400);
204
+ socket.destroy();
205
+ return;
206
+ }
207
+ if (p.routePipe.ct.encoding !== h["content-encoding"]) {
208
+ socket.write(errorRespMap.RESP_400);
209
+ socket.destroy();
210
+ return;
211
+ }
212
+ await decisionAccumulate(socket, p);
213
+ }
214
+ return {
215
+ accumulatorHeadGet,
216
+ decisionAccumulate,
217
+ accumulatorDefinedType,
218
+ accumulatorDefinedEncode,
219
+ accumulatorDefinedTypeEncode
220
+ };
221
+ }
@@ -0,0 +1,5 @@
1
+ import { Http } from "../../http";
2
+ declare function createRoute(url: string): Http.Route;
3
+ declare function createMiddleware(handle: Http.MiddlewareHandleFn | any): Http.Middleware;
4
+ declare function createEndpoint(method: Http.HttpMethod, url: string, handle: Http.EndpointHandleFn | null, ct?: Http.ContentConfig, cfg?: Http.EndpointOpt, accumulateHandle?: Http.AccumulateHandleFn): Http.Endpoint;
5
+ export { createRoute, createMiddleware, createEndpoint, };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRoute = createRoute;
4
+ exports.createMiddleware = createMiddleware;
5
+ exports.createEndpoint = createEndpoint;
6
+ function validateRouteUrl(url) {
7
+ if (url.includes("//")) {
8
+ throw new Error(`Route URL contains '//' double-slash: ${url}`);
9
+ }
10
+ // Split and remove empty segments to avoid false positives
11
+ const segments = url.split("/").filter(Boolean);
12
+ const nameRegex = /^[a-zA-Z0-9_]+$/;
13
+ for (const seg of segments) {
14
+ // Parameter segment ":id"
15
+ if (seg.startsWith(":")) {
16
+ const name = seg.slice(1);
17
+ if (!name)
18
+ throw new Error(`Empty parameter name ':' in route: ${url}`);
19
+ if (!nameRegex.test(name))
20
+ throw new Error(`Invalid parameter name '${name}' in route: ${url}`);
21
+ continue;
22
+ }
23
+ // Wildcard segment "*rest"
24
+ if (seg.startsWith("*")) {
25
+ const name = seg.slice(1);
26
+ // "*" is VALID → unnamed wildcard
27
+ if (name && !nameRegex.test(name)) {
28
+ throw new Error(`Invalid wildcard name '${name}' in route: ${url}`);
29
+ }
30
+ continue;
31
+ }
32
+ // Regular segments have no special constraints for now
33
+ }
34
+ return true;
35
+ }
36
+ function normalizeRouteUrl(url) {
37
+ if (typeof url !== "string")
38
+ url = String(url);
39
+ // Convert Windows-style slashes
40
+ url = url.replace(/\\/g, "/");
41
+ // Collapse duplicate slashes (but do not force a leading slash)
42
+ url = url.replace(/\/{2,}/g, "/");
43
+ // If the whole route is just "/", return as-is
44
+ if (url === "/")
45
+ return "/";
46
+ // Remove trailing slash
47
+ if (url.endsWith("/")) {
48
+ url = url.slice(0, -1);
49
+ }
50
+ return url;
51
+ }
52
+ function createRoute(url) {
53
+ url = normalizeRouteUrl(url);
54
+ validateRouteUrl(url);
55
+ return {
56
+ routes: [],
57
+ endpoints: [],
58
+ middlewares: [],
59
+ url,
60
+ addRoute(r) {
61
+ this.routes.push(r);
62
+ return this;
63
+ },
64
+ addMiddleware(mw) {
65
+ this.middlewares.push(mw);
66
+ return this;
67
+ },
68
+ addEndpoint(ep) {
69
+ this.endpoints.push(ep);
70
+ return this;
71
+ }
72
+ };
73
+ }
74
+ function createMiddleware(handle) {
75
+ return {
76
+ handle
77
+ };
78
+ }
79
+ function createEndpoint(method, url, handle, ct, cfg, accumulateHandle) {
80
+ url = normalizeRouteUrl(url);
81
+ validateRouteUrl(url);
82
+ return {
83
+ middlewares: [],
84
+ url,
85
+ method,
86
+ handle,
87
+ ct,
88
+ maxContentSize: cfg?.maxContentSize,
89
+ maxHeaderSize: cfg?.maxHeaderSize,
90
+ untilEnd: cfg?.untilEnd,
91
+ accumulateHandle,
92
+ addMiddleware(mw) {
93
+ this.middlewares.push(mw);
94
+ return this;
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,3 @@
1
+ import { Http } from "../../http";
2
+ declare function createPipeline(ep: Http.Endpoint, pipeFns: Http.MiddlewareHandleFn[]): any;
3
+ export { createPipeline };