@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.
- package/LICENSE +21 -0
- package/README.md +830 -0
- package/dist/auth-middlewares.d.ts +119 -0
- package/dist/auth-middlewares.d.ts.map +1 -0
- package/dist/auth-middlewares.js +168 -0
- package/dist/auth-middlewares.js.map +1 -0
- package/dist/cjs/auth-middlewares.d.ts +119 -0
- package/dist/cjs/auth-middlewares.d.ts.map +1 -0
- package/dist/cjs/auth-middlewares.js +168 -0
- package/dist/cjs/auth-middlewares.js.map +1 -0
- package/dist/cjs/helpers.d.ts +180 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +366 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +30 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/middlewares.d.ts +88 -0
- package/dist/cjs/middlewares.d.ts.map +1 -0
- package/dist/cjs/middlewares.js +262 -0
- package/dist/cjs/middlewares.js.map +1 -0
- package/dist/cjs/parsers.d.ts +177 -0
- package/dist/cjs/parsers.d.ts.map +1 -0
- package/dist/cjs/parsers.js +1021 -0
- package/dist/cjs/parsers.js.map +1 -0
- package/dist/cjs/router.d.ts +124 -0
- package/dist/cjs/router.d.ts.map +1 -0
- package/dist/cjs/router.js +1378 -0
- package/dist/cjs/router.js.map +1 -0
- package/dist/cjs/status.d.ts +77 -0
- package/dist/cjs/status.d.ts.map +1 -0
- package/dist/cjs/status.js +252 -0
- package/dist/cjs/status.js.map +1 -0
- package/dist/cjs/types.d.ts +135 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +109 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.d.ts +11 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +76 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/cjs/utils.node.d.ts +4 -0
- package/dist/cjs/utils.node.d.ts.map +1 -0
- package/dist/cjs/utils.node.js +99 -0
- package/dist/cjs/utils.node.js.map +1 -0
- package/dist/helpers.d.ts +180 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +366 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares.d.ts +88 -0
- package/dist/middlewares.d.ts.map +1 -0
- package/dist/middlewares.js +262 -0
- package/dist/middlewares.js.map +1 -0
- package/dist/parsers.d.ts +177 -0
- package/dist/parsers.d.ts.map +1 -0
- package/dist/parsers.js +1021 -0
- package/dist/parsers.js.map +1 -0
- package/dist/router.d.ts +124 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +1378 -0
- package/dist/router.js.map +1 -0
- package/dist/status.d.ts +77 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +252 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +109 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +76 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.node.d.ts +4 -0
- package/dist/utils.node.d.ts.map +1 -0
- package/dist/utils.node.js +66 -0
- package/dist/utils.node.js.map +1 -0
- 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
|