@bepalo/spine 1.0.3

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +830 -0
  3. package/dist/auth-middlewares.d.ts +119 -0
  4. package/dist/auth-middlewares.d.ts.map +1 -0
  5. package/dist/auth-middlewares.js +168 -0
  6. package/dist/auth-middlewares.js.map +1 -0
  7. package/dist/cjs/auth-middlewares.d.ts +119 -0
  8. package/dist/cjs/auth-middlewares.d.ts.map +1 -0
  9. package/dist/cjs/auth-middlewares.js +168 -0
  10. package/dist/cjs/auth-middlewares.js.map +1 -0
  11. package/dist/cjs/helpers.d.ts +180 -0
  12. package/dist/cjs/helpers.d.ts.map +1 -0
  13. package/dist/cjs/helpers.js +366 -0
  14. package/dist/cjs/helpers.js.map +1 -0
  15. package/dist/cjs/index.d.ts +11 -0
  16. package/dist/cjs/index.d.ts.map +1 -0
  17. package/dist/cjs/index.js +30 -0
  18. package/dist/cjs/index.js.map +1 -0
  19. package/dist/cjs/middlewares.d.ts +88 -0
  20. package/dist/cjs/middlewares.d.ts.map +1 -0
  21. package/dist/cjs/middlewares.js +262 -0
  22. package/dist/cjs/middlewares.js.map +1 -0
  23. package/dist/cjs/parsers.d.ts +177 -0
  24. package/dist/cjs/parsers.d.ts.map +1 -0
  25. package/dist/cjs/parsers.js +1021 -0
  26. package/dist/cjs/parsers.js.map +1 -0
  27. package/dist/cjs/router.d.ts +124 -0
  28. package/dist/cjs/router.d.ts.map +1 -0
  29. package/dist/cjs/router.js +1378 -0
  30. package/dist/cjs/router.js.map +1 -0
  31. package/dist/cjs/status.d.ts +77 -0
  32. package/dist/cjs/status.d.ts.map +1 -0
  33. package/dist/cjs/status.js +252 -0
  34. package/dist/cjs/status.js.map +1 -0
  35. package/dist/cjs/types.d.ts +135 -0
  36. package/dist/cjs/types.d.ts.map +1 -0
  37. package/dist/cjs/types.js +109 -0
  38. package/dist/cjs/types.js.map +1 -0
  39. package/dist/cjs/utils.d.ts +11 -0
  40. package/dist/cjs/utils.d.ts.map +1 -0
  41. package/dist/cjs/utils.js +76 -0
  42. package/dist/cjs/utils.js.map +1 -0
  43. package/dist/cjs/utils.node.d.ts +4 -0
  44. package/dist/cjs/utils.node.d.ts.map +1 -0
  45. package/dist/cjs/utils.node.js +99 -0
  46. package/dist/cjs/utils.node.js.map +1 -0
  47. package/dist/helpers.d.ts +180 -0
  48. package/dist/helpers.d.ts.map +1 -0
  49. package/dist/helpers.js +366 -0
  50. package/dist/helpers.js.map +1 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +30 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/middlewares.d.ts +88 -0
  56. package/dist/middlewares.d.ts.map +1 -0
  57. package/dist/middlewares.js +262 -0
  58. package/dist/middlewares.js.map +1 -0
  59. package/dist/parsers.d.ts +177 -0
  60. package/dist/parsers.d.ts.map +1 -0
  61. package/dist/parsers.js +1021 -0
  62. package/dist/parsers.js.map +1 -0
  63. package/dist/router.d.ts +124 -0
  64. package/dist/router.d.ts.map +1 -0
  65. package/dist/router.js +1378 -0
  66. package/dist/router.js.map +1 -0
  67. package/dist/status.d.ts +77 -0
  68. package/dist/status.d.ts.map +1 -0
  69. package/dist/status.js +252 -0
  70. package/dist/status.js.map +1 -0
  71. package/dist/types.d.ts +135 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +109 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/utils.d.ts +11 -0
  76. package/dist/utils.d.ts.map +1 -0
  77. package/dist/utils.js +76 -0
  78. package/dist/utils.js.map +1 -0
  79. package/dist/utils.node.d.ts +4 -0
  80. package/dist/utils.node.d.ts.map +1 -0
  81. package/dist/utils.node.js +66 -0
  82. package/dist/utils.node.js.map +1 -0
  83. package/package.json +57 -0
@@ -0,0 +1,1021 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseMultipart = exports.parseHeaders = exports.parseBody = exports.SUPPORTED_MEDIA_TYPES_LIST = exports.SUPPORTED_MEDIA_TYPES = exports.parseCookie = exports.parseCookieFromRequest = exports.parseQuery = void 0;
13
+ const rjson_1 = require("@bepalo/rjson");
14
+ const types_ts_1 = require("./types.js");
15
+ const helpers_ts_1 = require("./helpers.js");
16
+ const utils_ts_1 = require("./utils.js");
17
+ const CCColon = 58; // ":".charCodeAt(0);
18
+ const CCSemiColon = 59; // ";".charCodeAt(0);
19
+ const CCDQuote = 34; // '"'.charCodeAt(0);
20
+ const CCEqual = 61; // "=".charCodeAt(0);
21
+ const CCSpace = 32; // " ".charCodeAt(0);
22
+ const CCTab = 9; // "\t".charCodeAt(0);
23
+ const CCCR = 13; // "\r".charCodeAt(0);
24
+ const CCNL = 10; // "\n".charCodeAt(0);
25
+ /**
26
+ * Creates middleware that parses queries from the request url and adds them to the context.
27
+ *
28
+ * @template {string} Keys - Keys to extend context Query. "q"|"search"
29
+ * @template {Record<string, unknown>} ExtendQuery - Extend context Query
30
+ * @template {Record<string, unknown>} ExtendContext - Extend Router Context
31
+ *
32
+ * @returns {Function} A middleware function that adds parsed queries to context.query
33
+ */
34
+ const parseQuery = () => {
35
+ return (ctx) => {
36
+ if (ctx.query == null) {
37
+ ctx.query = {};
38
+ }
39
+ const searchParams = ctx.url.searchParams;
40
+ for (const key of searchParams.keys()) {
41
+ const values = searchParams.getAll(key);
42
+ ctx.query[key] =
43
+ values.length > 1 ? values[values.length - 1] : values[0];
44
+ }
45
+ };
46
+ };
47
+ exports.parseQuery = parseQuery;
48
+ /**
49
+ * Parses cookies from a Request object's Cookie header.
50
+ * @template {Record<string, string>} Expected
51
+ * @param {Request} req - The request object containing cookies
52
+ * @returns {Expected|undefined} An object with cookie name-value pairs, or undefined if no cookies
53
+ * @example
54
+ * const cookies = parseCookieFromRequest(req);
55
+ * // Returns: { session: "abc123", theme: "dark" }
56
+ */
57
+ const parseCookieFromRequest = (req, cookies) => {
58
+ const cookieHeader = req.headers.get("cookie");
59
+ if (cookieHeader != null) {
60
+ const _cookies = cookies !== null && cookies !== void 0 ? cookies : {};
61
+ for (const pair of cookieHeader.split(";")) {
62
+ const [rawName, rawValue, extra] = pair
63
+ .trim()
64
+ .split("=", 3)
65
+ .map((token) => token.trim());
66
+ if (rawName &&
67
+ rawValue !== undefined &&
68
+ rawValue !== "" &&
69
+ extra === undefined) {
70
+ _cookies[rawName] = decodeURIComponent(rawValue);
71
+ }
72
+ }
73
+ return _cookies;
74
+ }
75
+ return undefined;
76
+ };
77
+ exports.parseCookieFromRequest = parseCookieFromRequest;
78
+ /**
79
+ * Creates middleware that parses cookies from the request and adds them to the context.
80
+ *
81
+ * @template {string} Keys - Keys to extend context Cookie. "session"|"geo"
82
+ * @template {Record<string, string>} ExtendCookie - Extend context Cookie
83
+ * @template {Record<string, string>} ExtendContext - Extend Router Context
84
+ *
85
+ * @returns {Function} A middleware function that adds parsed cookies to context.cookie
86
+ */
87
+ const parseCookie = () => {
88
+ return (ctx) => {
89
+ if (ctx.cookie == null) {
90
+ ctx.cookie = {};
91
+ }
92
+ (0, exports.parseCookieFromRequest)(ctx.request, ctx.cookie);
93
+ };
94
+ };
95
+ exports.parseCookie = parseCookie;
96
+ /**
97
+ * Fronzen Set of supported media types for request body parsing.
98
+ */
99
+ exports.SUPPORTED_MEDIA_TYPES = Object.freeze(new Set([
100
+ "application/x-www-form-urlencoded",
101
+ "application/json",
102
+ "application/rjson",
103
+ "text/plain",
104
+ ]));
105
+ /**
106
+ * Fronzen Array of supported media types for request body parsing.
107
+ */
108
+ exports.SUPPORTED_MEDIA_TYPES_LIST = Object.freeze(Array.from(exports.SUPPORTED_MEDIA_TYPES.keys()));
109
+ /**
110
+ * Creates middleware that parses the request body based on Content-Type.
111
+ * Supports url-encoded forms, JSON, RJSON, and plain text.
112
+ *
113
+ * @template {Record<string, unknown>} ExtendContext - Extend Router Context
114
+ * @param {Object} [options] - Configuration options for body parsing
115
+ * @param {SupportedBodyMediaTypes|SupportedBodyMediaTypes[]} [options.accept] - Media types to accept (defaults to all supported)
116
+ * @param {number} [options.maxSize] - Maximum body size in bytes (defaults to 1MB)
117
+ * @param {number} [options.once] - Do not parse if parsed already. checks `ctx.body`
118
+ * @param {number} [options.clone] - Clone request before parsing it. Useful for forwarding.
119
+ * @returns {Function} A middleware function that adds parsed body to context.body
120
+ * @returns {Response} Returns a 415 response if content-type is not accepted
121
+ * @returns {Response} Returns a 413 response if body exceeds maxSize
122
+ * @returns {Response} Returns a 400 response if body is malformed
123
+ */
124
+ const parseBody = (options) => {
125
+ var _a;
126
+ const accept = new Set((options === null || options === void 0 ? void 0 : options.accept)
127
+ ? Array.isArray(options.accept)
128
+ ? options.accept
129
+ : [options.accept]
130
+ : exports.SUPPORTED_MEDIA_TYPES_LIST);
131
+ const maxSize = (_a = options === null || options === void 0 ? void 0 : options.maxSize) !== null && _a !== void 0 ? _a : 1024 * 1024; // Default 1MB
132
+ const once = options === null || options === void 0 ? void 0 : options.once;
133
+ const clone = options === null || options === void 0 ? void 0 : options.clone;
134
+ return (ctx) => __awaiter(void 0, void 0, void 0, function* () {
135
+ var _a, _b, _c, _d, _e, _f;
136
+ if (once && ctx.body)
137
+ return;
138
+ const { request } = ctx;
139
+ const contentType = (_a = request.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.split(";", 2)[0];
140
+ if (!(contentType && accept.has(contentType))) {
141
+ yield ((_b = request.body) === null || _b === void 0 ? void 0 : _b.cancel().catch(() => { }));
142
+ return (0, helpers_ts_1.status)(415);
143
+ }
144
+ const req = clone ? request.clone() : request;
145
+ try {
146
+ const contentLengthHeader = req.headers.get("content-length");
147
+ const contentLength = contentLengthHeader
148
+ ? parseInt(contentLengthHeader)
149
+ : undefined;
150
+ if (contentLength === 0) {
151
+ ctx.body = undefined;
152
+ return;
153
+ }
154
+ if (contentLength !== undefined && contentLength > maxSize) {
155
+ yield ((_c = request.body) === null || _c === void 0 ? void 0 : _c.cancel().catch(() => { }));
156
+ if (clone)
157
+ yield ((_d = req.body) === null || _d === void 0 ? void 0 : _d.cancel().catch(() => { }));
158
+ return (0, helpers_ts_1.status)(413);
159
+ }
160
+ switch (contentType) {
161
+ case "application/x-www-form-urlencoded": {
162
+ const searchParams = new URLSearchParams(yield req.text());
163
+ ctx.body = {};
164
+ for (const key of searchParams.keys()) {
165
+ ctx.body[key] = searchParams.get(key);
166
+ }
167
+ break;
168
+ }
169
+ case "application/json":
170
+ ctx.body = (yield req.json());
171
+ break;
172
+ case "application/rjson":
173
+ ctx.body = rjson_1.RJSON.parse(yield req.text());
174
+ break;
175
+ case "text/plain":
176
+ ctx.body = yield req.text();
177
+ break;
178
+ default:
179
+ ctx.body = undefined;
180
+ break;
181
+ }
182
+ }
183
+ catch (_g) {
184
+ yield ((_e = request.body) === null || _e === void 0 ? void 0 : _e.cancel().catch(() => { }));
185
+ if (clone)
186
+ yield ((_f = req.body) === null || _f === void 0 ? void 0 : _f.cancel().catch(() => { }));
187
+ return (0, helpers_ts_1.status)(400, "Malformed Payload");
188
+ }
189
+ });
190
+ };
191
+ exports.parseBody = parseBody;
192
+ var ParseHeaderState;
193
+ (function (ParseHeaderState) {
194
+ ParseHeaderState[ParseHeaderState["FindingKeyStart"] = 0] = "FindingKeyStart";
195
+ ParseHeaderState[ParseHeaderState["FindingKeyEnd"] = 1] = "FindingKeyEnd";
196
+ ParseHeaderState[ParseHeaderState["FindingValueStart"] = 2] = "FindingValueStart";
197
+ ParseHeaderState[ParseHeaderState["FindingValueEnd"] = 3] = "FindingValueEnd";
198
+ })(ParseHeaderState || (ParseHeaderState = {}));
199
+ const parseHeaders = (rawHeaders, contentDisposition) => {
200
+ const headers = new Headers();
201
+ if (rawHeaders.length > 0) {
202
+ const len_1 = rawHeaders.length - 1;
203
+ const textDecoder = new TextDecoder();
204
+ let keyStartIdx = 0;
205
+ let keyEndIdx = -1;
206
+ let valueStartIdx = -1;
207
+ let valueEndIdx = -1;
208
+ let key = "";
209
+ let value = "";
210
+ let parseHeaderState = ParseHeaderState.FindingKeyStart;
211
+ let i = 0;
212
+ over: while (i < rawHeaders.length) {
213
+ switch (parseHeaderState) {
214
+ case ParseHeaderState.FindingKeyStart: {
215
+ free: for (; i < rawHeaders.length; i++) {
216
+ const byte = rawHeaders[i];
217
+ switch (byte) {
218
+ case CCSpace:
219
+ case CCTab:
220
+ keyStartIdx = i + 1;
221
+ continue;
222
+ case CCColon: {
223
+ keyStartIdx = i;
224
+ keyEndIdx = i;
225
+ key = "";
226
+ parseHeaderState = ParseHeaderState.FindingValueStart;
227
+ i++;
228
+ break free;
229
+ }
230
+ case CCCR:
231
+ case CCNL:
232
+ throw new types_ts_1.HttpError(400, "Invalid header");
233
+ default:
234
+ keyStartIdx = i;
235
+ parseHeaderState = ParseHeaderState.FindingKeyEnd;
236
+ i++;
237
+ break free;
238
+ }
239
+ }
240
+ break;
241
+ }
242
+ case ParseHeaderState.FindingKeyEnd: {
243
+ free: for (; i < rawHeaders.length; i++) {
244
+ const byte = rawHeaders[i];
245
+ switch (byte) {
246
+ case CCColon: {
247
+ keyEndIdx = i;
248
+ valueStartIdx = i + 1;
249
+ parseHeaderState = ParseHeaderState.FindingValueStart;
250
+ i++;
251
+ break free;
252
+ }
253
+ case CCSpace:
254
+ case CCTab: {
255
+ keyEndIdx = i;
256
+ parseHeaderState = ParseHeaderState.FindingValueStart;
257
+ valueStartIdx = i + 1;
258
+ i++;
259
+ break free;
260
+ }
261
+ case CCCR:
262
+ case CCNL:
263
+ throw new types_ts_1.HttpError(400, "Invalid header");
264
+ default:
265
+ keyEndIdx = i + 1;
266
+ break;
267
+ }
268
+ }
269
+ break;
270
+ }
271
+ case ParseHeaderState.FindingValueStart: {
272
+ free: for (; i < rawHeaders.length; i++) {
273
+ const byte = rawHeaders[i];
274
+ switch (byte) {
275
+ case CCColon:
276
+ case CCSpace:
277
+ case CCTab:
278
+ valueStartIdx = i + 1;
279
+ continue;
280
+ default:
281
+ valueStartIdx = i;
282
+ parseHeaderState = ParseHeaderState.FindingValueEnd;
283
+ i++;
284
+ break free;
285
+ }
286
+ }
287
+ break;
288
+ }
289
+ case ParseHeaderState.FindingValueEnd:
290
+ free: for (; i < rawHeaders.length; i++) {
291
+ const byte = rawHeaders[i];
292
+ switch (byte) {
293
+ case CCCR:
294
+ case CCNL: {
295
+ valueEndIdx = i;
296
+ const keyChunk = rawHeaders.subarray(keyStartIdx, keyEndIdx);
297
+ const valueChunk = rawHeaders.subarray(valueStartIdx, valueEndIdx);
298
+ key = textDecoder.decode(keyChunk);
299
+ value = textDecoder.decode(valueChunk);
300
+ parseHeaderState = ParseHeaderState.FindingKeyStart;
301
+ if (key.length === 0) {
302
+ throw new types_ts_1.HttpError(400, "Invalid header");
303
+ }
304
+ headers.append(key, value);
305
+ // advance to new line
306
+ if (i < len_1) {
307
+ const nextByte = rawHeaders[i + 1];
308
+ if (nextByte === CCNL || nextByte === CCCR) {
309
+ i++;
310
+ }
311
+ }
312
+ i++;
313
+ key = "";
314
+ value = "";
315
+ keyStartIdx = i + 1;
316
+ keyEndIdx = -1;
317
+ valueStartIdx = -1;
318
+ valueEndIdx = -1;
319
+ break free;
320
+ }
321
+ default:
322
+ valueEndIdx = i + 1;
323
+ break;
324
+ }
325
+ }
326
+ break;
327
+ default:
328
+ break over;
329
+ }
330
+ }
331
+ if (parseHeaderState === ParseHeaderState.FindingValueStart ||
332
+ parseHeaderState === ParseHeaderState.FindingValueEnd) {
333
+ valueEndIdx = rawHeaders.length;
334
+ const keyChunk = rawHeaders.subarray(keyStartIdx, keyEndIdx);
335
+ const valueChunk = rawHeaders.subarray(valueStartIdx, valueEndIdx);
336
+ key = textDecoder.decode(keyChunk);
337
+ value = textDecoder.decode(valueChunk);
338
+ parseHeaderState = ParseHeaderState.FindingKeyStart;
339
+ if (key.length === 0) {
340
+ throw new types_ts_1.HttpError(400, "Invalid header");
341
+ }
342
+ headers.append(key, value);
343
+ }
344
+ }
345
+ if (contentDisposition != null && headers.has("content-disposition")) {
346
+ const dispositionHeader = headers.get("content-disposition");
347
+ let mode = 0;
348
+ let keyIdx0 = -1;
349
+ let keyIdx1 = -1;
350
+ let valueIdx0 = -1;
351
+ let valueIdx1 = -1;
352
+ // skip until first key
353
+ let i = 0;
354
+ for (; i < dispositionHeader.length; i++) {
355
+ const ch = dispositionHeader.charCodeAt(i);
356
+ if (ch === CCSemiColon) {
357
+ i++;
358
+ break;
359
+ }
360
+ }
361
+ // parse name and filename
362
+ for (; i < dispositionHeader.length; i++) {
363
+ const ch = dispositionHeader.charCodeAt(i);
364
+ switch (ch) {
365
+ case CCEqual:
366
+ switch (mode) {
367
+ case 1:
368
+ keyIdx1 = i;
369
+ mode = 2;
370
+ break;
371
+ default:
372
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
373
+ }
374
+ break;
375
+ case CCDQuote:
376
+ switch (mode) {
377
+ case 2:
378
+ valueIdx0 = i + 1;
379
+ mode = 4;
380
+ break;
381
+ case 4:
382
+ valueIdx1 = i;
383
+ mode = 5;
384
+ break;
385
+ default:
386
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
387
+ }
388
+ break;
389
+ case CCSemiColon:
390
+ switch (mode) {
391
+ case 3:
392
+ valueIdx1 = i;
393
+ mode = 5;
394
+ break;
395
+ case 0:
396
+ case 5:
397
+ break;
398
+ case 4:
399
+ default:
400
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
401
+ }
402
+ break;
403
+ case CCSpace:
404
+ case CCTab:
405
+ break;
406
+ case CCCR:
407
+ case CCNL:
408
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
409
+ default:
410
+ switch (mode) {
411
+ case 0:
412
+ keyIdx0 = i;
413
+ mode = 1;
414
+ break;
415
+ case 2:
416
+ valueIdx0 = i;
417
+ mode = 3;
418
+ break;
419
+ case 1:
420
+ case 3:
421
+ case 4:
422
+ break;
423
+ default:
424
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
425
+ }
426
+ }
427
+ if (mode === 5) {
428
+ if (keyIdx0 < 0 || keyIdx1 < 0 || valueIdx0 < 0 || valueIdx1 < 0) {
429
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
430
+ }
431
+ mode = 0;
432
+ const key = dispositionHeader.substring(keyIdx0, keyIdx1);
433
+ const value = dispositionHeader.substring(valueIdx0, valueIdx1);
434
+ contentDisposition[key] = value;
435
+ }
436
+ }
437
+ }
438
+ return headers;
439
+ };
440
+ exports.parseHeaders = parseHeaders;
441
+ var ParseMultipartState;
442
+ (function (ParseMultipartState) {
443
+ ParseMultipartState[ParseMultipartState["Initializing"] = 0] = "Initializing";
444
+ ParseMultipartState[ParseMultipartState["FindingBoundary"] = 1] = "FindingBoundary";
445
+ ParseMultipartState[ParseMultipartState["CheckingEndBoundary"] = 2] = "CheckingEndBoundary";
446
+ ParseMultipartState[ParseMultipartState["FindingCRLF"] = 3] = "FindingCRLF";
447
+ ParseMultipartState[ParseMultipartState["ParsingHeaders"] = 4] = "ParsingHeaders";
448
+ ParseMultipartState[ParseMultipartState["ParsingChunk"] = 5] = "ParsingChunk";
449
+ ParseMultipartState[ParseMultipartState["Done"] = 6] = "Done";
450
+ })(ParseMultipartState || (ParseMultipartState = {}));
451
+ const parseMultipart = ({ idGenerator, onStart, onEnd, onHeader, onData, onDataCompletion, }) => {
452
+ const prefix = new TextEncoder().encode("--");
453
+ const crlfPrefix = new TextEncoder().encode("\r\n--");
454
+ const endSuffix = new TextEncoder().encode("--");
455
+ const crlf1 = new Uint8Array(new TextEncoder().encode("\r\n"));
456
+ const crlf2 = new Uint8Array(new TextEncoder().encode("\r\n\r\n"));
457
+ return (ctx) => __awaiter(void 0, void 0, void 0, function* () {
458
+ var _a;
459
+ const { request } = ctx;
460
+ const headers = request.headers;
461
+ const contentTypeHedaer = (_a = headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.trim();
462
+ let error;
463
+ let boundary = new Uint8Array();
464
+ let boundaryPre = new Uint8Array();
465
+ let boundaryPost = new Uint8Array();
466
+ let response = undefined;
467
+ if (request.body == null) {
468
+ error = new types_ts_1.HttpError(400, "Empty body");
469
+ }
470
+ if (error == null &&
471
+ (contentTypeHedaer == null ||
472
+ !(contentTypeHedaer.length === 19
473
+ ? contentTypeHedaer.startsWith("multipart/form-data")
474
+ : contentTypeHedaer.startsWith("multipart/form-data;")))) {
475
+ error = new types_ts_1.HttpError(415, "Invalid header");
476
+ }
477
+ if (error == null && contentTypeHedaer != null) {
478
+ const separatorIndex = contentTypeHedaer.indexOf(";");
479
+ const boundaryHeader = separatorIndex != -1 &&
480
+ contentTypeHedaer.substring(separatorIndex).trim();
481
+ const equalsIndex = boundaryHeader && boundaryHeader.indexOf("=");
482
+ const boundaryStr = equalsIndex &&
483
+ equalsIndex > -1 &&
484
+ boundaryHeader.substring(equalsIndex + 1).trim();
485
+ if (!boundaryStr) {
486
+ error = new types_ts_1.HttpError(415, "Invalid boundary");
487
+ }
488
+ else {
489
+ const boundaryBytes = new TextEncoder().encode(boundaryStr);
490
+ const boundaryLen = boundaryBytes.length;
491
+ const crlfPrefixLen = crlfPrefix.length;
492
+ //
493
+ boundary = new Uint8Array(prefix.length + boundaryLen);
494
+ boundary.set(prefix, 0);
495
+ boundary.set(boundaryBytes, prefix.length);
496
+ //
497
+ boundaryPre = new Uint8Array(crlfPrefixLen + boundaryLen);
498
+ boundaryPre.set(crlfPrefix, 0);
499
+ boundaryPre.set(boundaryBytes, crlfPrefixLen);
500
+ //
501
+ boundaryPost = new Uint8Array(prefix.length + boundaryLen + crlf1.length);
502
+ boundaryPost.set(prefix, 0);
503
+ boundaryPost.set(boundaryBytes, prefix.length);
504
+ boundaryPost.set(crlf1, prefix.length + boundaryLen);
505
+ }
506
+ }
507
+ // initialize context
508
+ ctx.fields = new Map();
509
+ ctx.files = new Map();
510
+ // start processing body as a stream of chunks
511
+ if (error == null && response == null && onStart != null) {
512
+ response = yield onStart(ctx);
513
+ }
514
+ if (error == null && response == null) {
515
+ const reader = request.body.getReader();
516
+ const crlf_len_1 = crlf2.length - 1;
517
+ let parserState = ParseMultipartState.Initializing;
518
+ let boundaryIdx = 0;
519
+ let crlfIdx = 0;
520
+ // find boundary starting from offset and based on boundaryIdx previously set
521
+ const findBoundary = (chunk, boundary, offset = 0) => {
522
+ const boundary_len_1 = boundary.length - 1;
523
+ let i = 0;
524
+ let startIdx = -1;
525
+ let matching = false;
526
+ // check unfinished boundary
527
+ if (boundaryIdx > 0) {
528
+ for (i = offset; i < chunk.length; i++) {
529
+ if (chunk[i] === boundary[boundaryIdx]) {
530
+ if (!matching && boundaryIdx < boundary_len_1) {
531
+ matching = true;
532
+ startIdx = i;
533
+ }
534
+ else if (boundaryIdx === boundary_len_1) {
535
+ boundaryIdx = 0;
536
+ return [!matching ? 0 : startIdx, i + 1, true, false];
537
+ }
538
+ boundaryIdx++;
539
+ }
540
+ else if (matching) {
541
+ boundaryIdx = 0;
542
+ startIdx = -1;
543
+ matching = false;
544
+ }
545
+ }
546
+ // boundary spans whole chunk
547
+ if (matching && i >= chunk.length) {
548
+ return [startIdx < 0 ? 0 : startIdx, -1, false, true];
549
+ }
550
+ else {
551
+ boundaryIdx = 0;
552
+ matching = false;
553
+ }
554
+ }
555
+ for (i = offset; i < chunk.length; i++) {
556
+ if (chunk[i] === boundary[boundaryIdx]) {
557
+ if (!matching) {
558
+ matching = true;
559
+ startIdx = i;
560
+ }
561
+ else if (boundaryIdx === boundary_len_1) {
562
+ boundaryIdx = 0;
563
+ return [startIdx, i + 1, false, false];
564
+ }
565
+ boundaryIdx++;
566
+ }
567
+ else if (matching) {
568
+ boundaryIdx = 0;
569
+ startIdx = -1;
570
+ matching = false;
571
+ }
572
+ }
573
+ return [startIdx, -1, false, false];
574
+ };
575
+ // find \r\n\r\n starting from offset and based on crlfIdx previously set
576
+ const findCRLF = (chunk, offset = 0) => {
577
+ let i = 0;
578
+ let startIdx = -1;
579
+ let matching = false;
580
+ if (crlfIdx > 0) {
581
+ for (i = offset; i < chunk.length; i++) {
582
+ if (chunk[i] === crlf2[crlfIdx]) {
583
+ if (!matching && crlfIdx < crlf_len_1) {
584
+ matching = true;
585
+ startIdx = i;
586
+ }
587
+ else if (crlfIdx === crlf_len_1) {
588
+ crlfIdx = 0;
589
+ return [!matching ? 0 : startIdx, i + 1, true];
590
+ }
591
+ crlfIdx++;
592
+ }
593
+ else if (matching) {
594
+ crlfIdx = 0;
595
+ startIdx = -1;
596
+ matching = false;
597
+ }
598
+ }
599
+ crlfIdx = 0;
600
+ matching = false;
601
+ }
602
+ for (i = offset; i < chunk.length; i++) {
603
+ if (chunk[i] === crlf2[crlfIdx]) {
604
+ if (!matching) {
605
+ matching = true;
606
+ startIdx = i;
607
+ }
608
+ else if (crlfIdx === crlf_len_1) {
609
+ crlfIdx = 0;
610
+ return [startIdx, i + 1, false];
611
+ }
612
+ crlfIdx++;
613
+ }
614
+ else if (matching) {
615
+ crlfIdx = 0;
616
+ startIdx = -1;
617
+ matching = false;
618
+ }
619
+ }
620
+ return [startIdx, -1, false];
621
+ };
622
+ ////////////////////////
623
+ let streamIsDone = false;
624
+ const getNextChunk = () => __awaiter(void 0, void 0, void 0, function* () {
625
+ const { done, value } = yield reader.read();
626
+ if (done) {
627
+ streamIsDone = done;
628
+ }
629
+ return value;
630
+ });
631
+ ////////////////////////////
632
+ let initializationStepDone = false;
633
+ let activeHeaders = undefined;
634
+ let activeId = "";
635
+ let activeName = "";
636
+ let activeFilename = undefined;
637
+ let activeChunk = undefined;
638
+ let activeHeaderStart = -1;
639
+ let activeHeaderEnd = -1;
640
+ let activeBodyStart = -1;
641
+ let activeBodyEnd = -1;
642
+ let leftOverBoundary = undefined;
643
+ let leftOverHeader = undefined;
644
+ let expectBoundaryCRLF = 0;
645
+ let headersChunks = [];
646
+ let fileInfo = undefined;
647
+ ///////////////////////////////////////
648
+ away: while (parserState !== ParseMultipartState.Done) {
649
+ switch (parserState) {
650
+ case ParseMultipartState.Initializing: {
651
+ activeChunk = yield getNextChunk();
652
+ parserState = ParseMultipartState.FindingBoundary;
653
+ activeHeaderStart = -1;
654
+ activeHeaderEnd = -1;
655
+ activeBodyStart = -1;
656
+ activeBodyEnd = -1;
657
+ break;
658
+ }
659
+ case ParseMultipartState.FindingBoundary: {
660
+ if (activeChunk == null) {
661
+ if (!streamIsDone) {
662
+ error = new types_ts_1.HttpError(400, "Invalid body");
663
+ }
664
+ break away;
665
+ }
666
+ // find start boundary
667
+ const [boundaryStart, boundaryEnd, leftOverMatch, leftOverCompleteMatch,] = findBoundary(activeChunk, initializationStepDone ? boundaryPre : boundary, activeHeaderEnd < 0 ? 0 : activeHeaderEnd + 2);
668
+ if (streamIsDone &&
669
+ ((boundaryEnd != -1 &&
670
+ activeChunk[boundaryEnd] === endSuffix[0] &&
671
+ activeChunk[boundaryEnd + 1] === endSuffix[1]) ||
672
+ boundaryStart + 2 >= boundaryEnd)) {
673
+ break away;
674
+ }
675
+ if (!initializationStepDone && boundaryStart < 0) {
676
+ error = new types_ts_1.HttpError(400, "Invalid body");
677
+ break away;
678
+ }
679
+ // check for leftOverBoundary
680
+ if (leftOverBoundary != null &&
681
+ !leftOverMatch &&
682
+ !leftOverCompleteMatch &&
683
+ boundaryIdx === 0) {
684
+ if (fileInfo != null) {
685
+ fileInfo.size += leftOverBoundary.length;
686
+ }
687
+ response = yield onData(ctx, {
688
+ chunk: leftOverBoundary,
689
+ headers: activeHeaders,
690
+ id: activeId,
691
+ name: activeName,
692
+ filename: activeFilename,
693
+ file: fileInfo,
694
+ });
695
+ if (response != null) {
696
+ break away;
697
+ }
698
+ leftOverBoundary = undefined;
699
+ }
700
+ else if (leftOverMatch) {
701
+ if (initializationStepDone && onDataCompletion != null) {
702
+ response = yield onDataCompletion(ctx, {
703
+ headers: activeHeaders,
704
+ id: activeId,
705
+ name: activeName,
706
+ filename: activeFilename,
707
+ file: fileInfo,
708
+ });
709
+ if (response != null) {
710
+ break away;
711
+ }
712
+ }
713
+ leftOverBoundary = undefined;
714
+ }
715
+ if (leftOverCompleteMatch) {
716
+ activeBodyEnd = -1;
717
+ activeHeaderStart = -1;
718
+ }
719
+ else {
720
+ activeBodyEnd = boundaryStart;
721
+ activeHeaderStart = boundaryEnd + 2;
722
+ }
723
+ // keep looking if not found
724
+ if (boundaryStart < 0) {
725
+ if (streamIsDone) {
726
+ error = new types_ts_1.HttpError(400, "Invalid body");
727
+ break away;
728
+ }
729
+ if (initializationStepDone && activeBodyStart != -1) {
730
+ const chunk = activeChunk.subarray(activeBodyStart, activeChunk.length - boundaryIdx);
731
+ if (fileInfo != null) {
732
+ fileInfo.size += chunk.length;
733
+ }
734
+ response = yield onData(ctx, {
735
+ chunk,
736
+ headers: activeHeaders,
737
+ id: activeId,
738
+ name: activeName,
739
+ filename: activeFilename,
740
+ file: fileInfo,
741
+ });
742
+ if (response != null) {
743
+ break away;
744
+ }
745
+ }
746
+ activeChunk = yield getNextChunk();
747
+ activeHeaderStart = -1;
748
+ activeHeaderEnd = -1;
749
+ activeBodyStart = 0;
750
+ activeBodyEnd = -1;
751
+ continue;
752
+ }
753
+ // keep looking for the boundaryEnd in the next chunk
754
+ if (boundaryEnd < 0) {
755
+ if (streamIsDone) {
756
+ error = new types_ts_1.HttpError(400, "Invalid body");
757
+ break away;
758
+ }
759
+ if (leftOverBoundary != null && boundaryStart !== -1) {
760
+ const currentLeftOver = activeChunk.subarray(boundaryStart, activeChunk.length - boundaryStart);
761
+ const newLeftOver = new Uint8Array(leftOverBoundary.length + currentLeftOver.length);
762
+ newLeftOver.set(leftOverBoundary, 0);
763
+ newLeftOver.set(currentLeftOver, leftOverBoundary.length);
764
+ leftOverBoundary = newLeftOver;
765
+ }
766
+ else {
767
+ leftOverBoundary = activeChunk.subarray(activeChunk.length - boundaryIdx);
768
+ }
769
+ if (!leftOverCompleteMatch && activeBodyEnd > 0) {
770
+ const chunk = activeChunk.subarray(activeBodyStart, activeBodyEnd);
771
+ if (fileInfo != null) {
772
+ fileInfo.size += chunk.length;
773
+ }
774
+ response = yield onData(ctx, {
775
+ chunk,
776
+ headers: activeHeaders,
777
+ id: activeId,
778
+ name: activeName,
779
+ filename: activeFilename,
780
+ file: fileInfo,
781
+ });
782
+ if (response != null) {
783
+ break away;
784
+ }
785
+ }
786
+ activeChunk = yield getNextChunk();
787
+ activeHeaderStart = -1;
788
+ activeHeaderEnd = -1;
789
+ activeBodyStart = 0;
790
+ activeBodyEnd = -1;
791
+ continue;
792
+ }
793
+ if (!leftOverCompleteMatch &&
794
+ boundaryEnd > activeChunk.length - crlf1.length) {
795
+ expectBoundaryCRLF =
796
+ crlf1.length - (activeChunk.length - boundaryEnd);
797
+ }
798
+ else {
799
+ expectBoundaryCRLF = 0;
800
+ }
801
+ parserState = initializationStepDone
802
+ ? ParseMultipartState.ParsingChunk
803
+ : ParseMultipartState.FindingCRLF;
804
+ if (!initializationStepDone) {
805
+ initializationStepDone = true;
806
+ }
807
+ break;
808
+ }
809
+ case ParseMultipartState.FindingCRLF: {
810
+ if (activeChunk == null) {
811
+ if (!streamIsDone) {
812
+ error = new types_ts_1.HttpError(400, "Invalid body");
813
+ }
814
+ break away;
815
+ }
816
+ if (activeHeaderStart < 0) {
817
+ if (expectBoundaryCRLF > 0) {
818
+ activeHeaderStart = expectBoundaryCRLF;
819
+ expectBoundaryCRLF = 0;
820
+ }
821
+ else {
822
+ activeHeaderStart = 0;
823
+ }
824
+ }
825
+ // find crlf2
826
+ const [crlfStart, crlfEnd, leftOverHeaderMatch] = findCRLF(activeChunk, activeHeaderStart);
827
+ // check for leftOverHeader
828
+ if (leftOverHeader != null && !leftOverHeaderMatch) {
829
+ headersChunks.push(leftOverHeader);
830
+ leftOverHeader = undefined;
831
+ }
832
+ // keep looking if not found
833
+ if (crlfStart < 0) {
834
+ if (streamIsDone) {
835
+ error = new types_ts_1.HttpError(400, "Invalid body");
836
+ break away;
837
+ }
838
+ const headerChunk = activeChunk.subarray(activeHeaderStart, activeChunk.length);
839
+ headersChunks.push(headerChunk);
840
+ activeChunk = yield getNextChunk();
841
+ activeHeaderStart = -1;
842
+ activeHeaderEnd = -1;
843
+ activeBodyStart = -1;
844
+ activeBodyEnd = -1;
845
+ continue;
846
+ }
847
+ activeHeaderEnd = crlfStart;
848
+ // keep looking for the crlfEnd in the next chunk
849
+ if (crlfEnd < 0) {
850
+ if (streamIsDone) {
851
+ error = new types_ts_1.HttpError(400, "Invalid body");
852
+ break away;
853
+ }
854
+ if (leftOverHeader != null) {
855
+ const currentLeftOver = activeChunk.subarray(activeHeaderEnd);
856
+ const newLeftOver = new Uint8Array(leftOverHeader.length + currentLeftOver.length);
857
+ newLeftOver.set(leftOverHeader, 0);
858
+ newLeftOver.set(currentLeftOver, leftOverHeader.length);
859
+ leftOverBoundary = newLeftOver;
860
+ }
861
+ else {
862
+ leftOverHeader = activeChunk.subarray(activeHeaderEnd);
863
+ }
864
+ const headerChunk = activeChunk.subarray(activeHeaderStart, activeHeaderEnd);
865
+ headersChunks.push(headerChunk);
866
+ activeChunk = yield getNextChunk();
867
+ activeHeaderStart = 0;
868
+ activeHeaderEnd = -1;
869
+ activeBodyStart = -1;
870
+ activeBodyEnd = -1;
871
+ continue;
872
+ }
873
+ activeBodyStart = crlfEnd;
874
+ if (activeHeaderStart != -1) {
875
+ const headerChunk = activeChunk.subarray(activeHeaderStart, activeHeaderEnd);
876
+ headersChunks.push(headerChunk);
877
+ }
878
+ parserState = ParseMultipartState.ParsingHeaders;
879
+ break;
880
+ }
881
+ case ParseMultipartState.ParsingHeaders: {
882
+ if (activeChunk == null) {
883
+ if (!streamIsDone) {
884
+ error = new types_ts_1.HttpError(400, "Invalid body");
885
+ }
886
+ break away;
887
+ }
888
+ let buffer = headersChunks.length === 1 ? headersChunks[0] : undefined;
889
+ if (headersChunks.length > 1) {
890
+ let requiredSize = 0;
891
+ let bufferOffset = 0;
892
+ for (const headerChunk of headersChunks) {
893
+ requiredSize += headerChunk.length;
894
+ }
895
+ buffer = new Uint8Array(requiredSize);
896
+ for (const headerChunk of headersChunks) {
897
+ if (headerChunk.length === 0) {
898
+ continue;
899
+ }
900
+ buffer.set(headerChunk, bufferOffset);
901
+ bufferOffset += headerChunk.length;
902
+ }
903
+ }
904
+ headersChunks = [];
905
+ leftOverHeader = undefined;
906
+ const contentDisposition = {};
907
+ activeHeaders = (0, exports.parseHeaders)(new Uint8Array(buffer), contentDisposition);
908
+ if (contentDisposition["name"] == null) {
909
+ throw new types_ts_1.HttpError(400, "Invalid Content-Disposition header");
910
+ }
911
+ // intialize for formdata part
912
+ activeName = contentDisposition.name;
913
+ activeFilename = contentDisposition.filename;
914
+ activeId =
915
+ idGenerator != null
916
+ ? idGenerator({
917
+ headers: activeHeaders,
918
+ name: activeName,
919
+ filename: activeFilename,
920
+ })
921
+ : (0, utils_ts_1.toBase64UUID)(crypto.randomUUID());
922
+ if (activeFilename != null) {
923
+ const contentTypeHeader = activeHeaders.get("content-type");
924
+ const contentLengthHeader = activeHeaders.get("content-length");
925
+ const size = 0;
926
+ const totalSize = contentLengthHeader
927
+ ? parseInt(contentLengthHeader)
928
+ : null;
929
+ const type = (contentTypeHeader
930
+ ? contentTypeHeader
931
+ : "application/octet-stream");
932
+ fileInfo = {
933
+ name: activeFilename,
934
+ size,
935
+ totalSize,
936
+ type,
937
+ };
938
+ ctx.files.set(activeId, fileInfo);
939
+ }
940
+ else {
941
+ ctx.fields.set(activeName, null);
942
+ }
943
+ // invoke callback
944
+ if (onHeader != null) {
945
+ response = yield onHeader(ctx, {
946
+ headers: activeHeaders,
947
+ id: activeId,
948
+ name: activeName,
949
+ filename: activeFilename,
950
+ file: fileInfo,
951
+ });
952
+ if (response != null) {
953
+ break away;
954
+ }
955
+ }
956
+ parserState = ParseMultipartState.FindingBoundary;
957
+ break;
958
+ }
959
+ case ParseMultipartState.ParsingChunk: {
960
+ if (activeChunk == null) {
961
+ if (!streamIsDone) {
962
+ error = new types_ts_1.HttpError(400, "Invalid body");
963
+ }
964
+ break away;
965
+ }
966
+ if (activeBodyStart != -1 && activeBodyStart < activeBodyEnd) {
967
+ const chunk = activeChunk.subarray(activeBodyStart, activeBodyEnd);
968
+ if (fileInfo != null) {
969
+ fileInfo.size += chunk.length;
970
+ }
971
+ response = yield onData(ctx, {
972
+ chunk,
973
+ headers: activeHeaders,
974
+ id: activeId,
975
+ name: activeName,
976
+ filename: activeFilename,
977
+ file: fileInfo,
978
+ });
979
+ if (response != null) {
980
+ break away;
981
+ }
982
+ if (onDataCompletion != null) {
983
+ response = yield onDataCompletion(ctx, {
984
+ headers: activeHeaders,
985
+ id: activeId,
986
+ name: activeName,
987
+ filename: activeFilename,
988
+ file: fileInfo,
989
+ });
990
+ if (response != null) {
991
+ break away;
992
+ }
993
+ }
994
+ }
995
+ parserState = ParseMultipartState.FindingCRLF;
996
+ break;
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ // handle completion and return a response
1002
+ if (onEnd != null) {
1003
+ response = yield onEnd(ctx, {
1004
+ success: error == null,
1005
+ error,
1006
+ });
1007
+ }
1008
+ if (response instanceof Response) {
1009
+ return response;
1010
+ }
1011
+ else if (response === types_ts_1.Break_Pipe) {
1012
+ return types_ts_1.Break_Pipe;
1013
+ }
1014
+ else if (response === types_ts_1.Break_Pipeline) {
1015
+ return types_ts_1.Break_Pipeline;
1016
+ }
1017
+ return error == null ? (0, helpers_ts_1.status)(200) : (0, helpers_ts_1.status)(error.status, error.message);
1018
+ });
1019
+ };
1020
+ exports.parseMultipart = parseMultipart;
1021
+ //# sourceMappingURL=parsers.js.map