fulmine.js 5.19.3 → 5.19.4
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/package.json +1 -1
- package/src/application.js +25 -36
- package/src/cli.js +22 -16
- package/src/cluster.js +2 -2
- package/src/compression.js +102 -61
- package/src/declarative.js +47 -24
- package/src/hot-settings.js +5 -5
- package/src/lazy-readable.js +8 -6
- package/src/lazy-writable.js +3 -3
- package/src/middlewares.js +94 -55
- package/src/nest.js +3 -2
- package/src/node-shim.js +10 -5
- package/src/optimizer.js +9 -7
- package/src/options.d.ts +9 -4
- package/src/request-utils.js +3 -2
- package/src/request.js +49 -38
- package/src/response.js +145 -109
- package/src/route.js +3 -3
- package/src/router-utils.js +62 -14
- package/src/router.js +46 -28
- package/src/server-shape.js +14 -10
- package/src/server-timing.js +16 -4
- package/src/socket.js +3 -3
- package/src/testing.js +4 -3
- package/src/usage.js +10 -5
- package/src/utils.js +120 -23
- package/src/verify.js +4 -3
- package/src/view.js +1 -1
- package/src/walk.js +8 -7
- package/src/websocket.js +17 -8
- package/src/work.js +3 -3
package/src/middlewares.js
CHANGED
|
@@ -26,6 +26,7 @@ const mime = require("mime-types");
|
|
|
26
26
|
const compressible = require("compressible");
|
|
27
27
|
const ms = require("ms");
|
|
28
28
|
const qs = require("qs");
|
|
29
|
+
const statuses = require("statuses");
|
|
29
30
|
const parseQuery = require("./parse-query.js");
|
|
30
31
|
const { kGetSafe } = require("./usage.js");
|
|
31
32
|
const { AsyncResource } = require("async_hooks");
|
|
@@ -48,6 +49,7 @@ const {
|
|
|
48
49
|
NullObject,
|
|
49
50
|
asStatError,
|
|
50
51
|
httpError,
|
|
52
|
+
httpErrorName,
|
|
51
53
|
memoizeByString,
|
|
52
54
|
containsDotFile,
|
|
53
55
|
negotiateEncoding,
|
|
@@ -74,6 +76,26 @@ const PRECOMPRESSED = [
|
|
|
74
76
|
|
|
75
77
|
/** @typedef {import("./request.js")} Request */
|
|
76
78
|
/** @typedef {import("./response.js")} Response */
|
|
79
|
+
/** @typedef {import("./utils.js").HttpError} HttpError */
|
|
80
|
+
/** @typedef {import("./options").BodyParserOptions} BodyParserOptions */
|
|
81
|
+
/**
|
|
82
|
+
* A decompressor from fast-zlib, carrying the flag its final flush passes, see createInflate.
|
|
83
|
+
* @typedef {(import("fast-zlib").Inflate|import("fast-zlib").Gunzip|import("fast-zlib").BrotliDecompress)
|
|
84
|
+
* & {_finishFlag?: number}} Inflater
|
|
85
|
+
*/
|
|
86
|
+
/**
|
|
87
|
+
* What a parser does with the collected bytes: turns them into req.body and carries on. `body` is
|
|
88
|
+
* deliberately not a field of Request, see the comment there, so it is added here. The charset is
|
|
89
|
+
* undefined only for raw, which never decodes: the others settle it before a byte is read.
|
|
90
|
+
* @typedef {(
|
|
91
|
+
* req: Request & {body?: unknown},
|
|
92
|
+
* res: Response,
|
|
93
|
+
* next: (err?: unknown) => void,
|
|
94
|
+
* options: BodyParserOptions,
|
|
95
|
+
* buf: Buffer,
|
|
96
|
+
* encoding: string|undefined
|
|
97
|
+
* ) => void} BodyHandler
|
|
98
|
+
*/
|
|
77
99
|
|
|
78
100
|
// The failures express.static answers by moving on to the next handler rather than by reporting
|
|
79
101
|
// them, when fallthrough is on. They all mean the same thing: the request is not a file here.
|
|
@@ -123,7 +145,7 @@ let iconv;
|
|
|
123
145
|
* iconv-lite, loaded only when a request names a charset the Buffer cannot decode, so the common
|
|
124
146
|
* utf-8 request never pays for it.
|
|
125
147
|
*
|
|
126
|
-
* @returns {
|
|
148
|
+
* @returns {typeof import("iconv-lite")}
|
|
127
149
|
*/
|
|
128
150
|
function loadIconv() {
|
|
129
151
|
if (!iconv) iconv = require("iconv-lite");
|
|
@@ -200,7 +222,7 @@ function decodeBody(buf, encoding) {
|
|
|
200
222
|
case "iso-8859-1":
|
|
201
223
|
return buf.toString("latin1");
|
|
202
224
|
default:
|
|
203
|
-
return loadIconv().decode(buf, encoding);
|
|
225
|
+
return loadIconv().decode(buf, /** @type {import("iconv-lite").Encoding} */ (encoding));
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
|
|
@@ -210,29 +232,51 @@ function decodeBody(buf, encoding) {
|
|
|
210
232
|
*
|
|
211
233
|
* @param {Request} req
|
|
212
234
|
* @param {Response} res
|
|
213
|
-
* @param {(err?:
|
|
214
|
-
* @param {
|
|
235
|
+
* @param {(err?: unknown) => void} next
|
|
236
|
+
* @param {BodyParserOptions} options the parser options, settled by createBodyParser
|
|
215
237
|
* @param {Buffer} buf
|
|
238
|
+
* @param {string|undefined} encoding the charset the body is about to be decoded with, undefined
|
|
239
|
+
* for raw, which never decodes
|
|
216
240
|
* @returns {boolean}
|
|
217
241
|
*/
|
|
218
|
-
function runVerify(req, res, next, options, buf) {
|
|
242
|
+
function runVerify(req, res, next, options, buf, encoding) {
|
|
219
243
|
if (!options.verify) {
|
|
220
244
|
return true;
|
|
221
245
|
}
|
|
222
246
|
try {
|
|
223
|
-
|
|
247
|
+
// the charset goes too, as body-parser hands it over: a hook checking a signature over
|
|
248
|
+
// the decoded text needs it. raw gets null, which is what body-parser gives it there
|
|
249
|
+
options.verify(req, res, buf, encoding ?? null);
|
|
224
250
|
return true;
|
|
225
251
|
} catch (e) {
|
|
226
|
-
|
|
227
|
-
next(
|
|
228
|
-
asBodyError(err, err.status ?? err.statusCode ?? 403, err.type ?? "entity.verify.failed", {
|
|
229
|
-
body: buf
|
|
230
|
-
})
|
|
231
|
-
);
|
|
252
|
+
next(verifyError(e, buf));
|
|
232
253
|
return false;
|
|
233
254
|
}
|
|
234
255
|
}
|
|
235
256
|
|
|
257
|
+
/**
|
|
258
|
+
* The error a verify hook's throw becomes, as http-errors shapes it for body-parser. An Error is
|
|
259
|
+
* kept, with its own status when that is one a client can be answered with; a thrown string
|
|
260
|
+
* becomes a 403 with that message, and anything else a plain 403.
|
|
261
|
+
*
|
|
262
|
+
* @param {unknown} thrown
|
|
263
|
+
* @param {Buffer} buf the body, which rides on the error as body-parser puts it there
|
|
264
|
+
* @returns {HttpError}
|
|
265
|
+
*/
|
|
266
|
+
function verifyError(thrown, buf) {
|
|
267
|
+
const own = thrown instanceof Error ? /** @type {HttpError} */ (thrown) : undefined;
|
|
268
|
+
let status = own ? own.status || own.statusCode || 403 : 403;
|
|
269
|
+
// http-errors answers 500 for a status it cannot answer with: not a number, or outside 4xx
|
|
270
|
+
// and 5xx with no message of its own
|
|
271
|
+
if (typeof status !== "number" || (!statuses.message[status] && (status < 400 || status >= 600))) {
|
|
272
|
+
status = 500;
|
|
273
|
+
}
|
|
274
|
+
const err = own ?? httpError(status, typeof thrown === "string" ? thrown : undefined);
|
|
275
|
+
// read off whatever was thrown, as body-parser reads it
|
|
276
|
+
const type = /** @type {{type?: string}|null|undefined} */ (thrown)?.type || "entity.verify.failed";
|
|
277
|
+
return asBodyError(err, status, type, { body: buf });
|
|
278
|
+
}
|
|
279
|
+
|
|
236
280
|
/**
|
|
237
281
|
* The message a strict violation gets, which is the one V8 would have produced had the body been
|
|
238
282
|
* invalid JSON rather than merely not an object.
|
|
@@ -255,22 +299,13 @@ function strictSyntaxMessage(text, char) {
|
|
|
255
299
|
JSON.parse(partial);
|
|
256
300
|
} catch (e) {
|
|
257
301
|
// put the real characters back where the placeholders were named
|
|
258
|
-
return /** @type {
|
|
302
|
+
return /** @type {SyntaxError} */ (e).message.replace(/#+/g, (/** @type {string} */ placeholder) =>
|
|
259
303
|
text.substring(index, index + placeholder.length)
|
|
260
304
|
);
|
|
261
305
|
}
|
|
262
306
|
return "strict violation";
|
|
263
307
|
}
|
|
264
308
|
|
|
265
|
-
// The name http-errors gives each status body-parser answers with. An application reading
|
|
266
|
-
// err.name, or a logger printing it, sees "PayloadTooLargeError" from Express and would have
|
|
267
|
-
// seen a bare "Error" here.
|
|
268
|
-
const BODY_ERROR_NAMES = {
|
|
269
|
-
400: "BadRequestError",
|
|
270
|
-
413: "PayloadTooLargeError",
|
|
271
|
-
415: "UnsupportedMediaTypeError"
|
|
272
|
-
};
|
|
273
|
-
|
|
274
309
|
/**
|
|
275
310
|
* The error a body parser hands to next(), shaped as body-parser shapes it: with a status, since
|
|
276
311
|
* `res.status(err.status || 500)` would otherwise answer 500 to a request that was merely too
|
|
@@ -283,10 +318,11 @@ const BODY_ERROR_NAMES = {
|
|
|
283
318
|
* @returns {Error}
|
|
284
319
|
*/
|
|
285
320
|
function bodyError(message, status, type, extra) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
321
|
+
/** @type {HttpError} */
|
|
322
|
+
const err = new Error(message);
|
|
323
|
+
// the name http-errors gives it: an application reading err.name, or a logger printing it,
|
|
324
|
+
// sees "PayloadTooLargeError" from Express and would have seen a bare "Error" here
|
|
325
|
+
err.name = httpErrorName(status);
|
|
290
326
|
return asBodyError(err, status, type, extra);
|
|
291
327
|
}
|
|
292
328
|
|
|
@@ -296,7 +332,7 @@ function bodyError(message, status, type, extra) {
|
|
|
296
332
|
* its stack and any property the thrower put on it are all still there when the application
|
|
297
333
|
* reads it.
|
|
298
334
|
*
|
|
299
|
-
* @param {
|
|
335
|
+
* @param {HttpError} err the error the fields go on: JSON.parse's SyntaxError, or a verify hook's own
|
|
300
336
|
* @param {number} status
|
|
301
337
|
* @param {string} type body-parser's own name for the kind of failure
|
|
302
338
|
* @param {object} [extra] anything else body-parser puts on that particular error
|
|
@@ -411,7 +447,7 @@ function pickPrecompressed(filePath, accept, ttl, statTtl) {
|
|
|
411
447
|
*
|
|
412
448
|
* @param {string} dir the directory to look in
|
|
413
449
|
* @param {string[]} indexList the index names, in order
|
|
414
|
-
* @returns {{stat:
|
|
450
|
+
* @returns {{stat: import("fs").Stats, name: string, candidate: string}|null} the file to serve, or null
|
|
415
451
|
*/
|
|
416
452
|
function findIndexFile(dir, indexList) {
|
|
417
453
|
let lastError;
|
|
@@ -445,7 +481,7 @@ function findIndexFile(dir, indexList) {
|
|
|
445
481
|
*
|
|
446
482
|
* @param {string} root directory to serve from
|
|
447
483
|
* @param {import("./options").StaticOptions} [options]
|
|
448
|
-
* @returns {(req:
|
|
484
|
+
* @returns {(req: Request, res: Response, next: (err?: unknown) => void) => void}
|
|
449
485
|
*/
|
|
450
486
|
function serveStatic(root, options) {
|
|
451
487
|
// serve-static's own messages, thrown where the middleware is written rather than where a
|
|
@@ -478,21 +514,22 @@ function serveStatic(root, options) {
|
|
|
478
514
|
if (options.setHeaders !== undefined && typeof options.setHeaders !== "function") {
|
|
479
515
|
throw new TypeError("option setHeaders must be function");
|
|
480
516
|
}
|
|
517
|
+
// serve-static's own option, which res.sendFile does not take, so it goes down under a name
|
|
518
|
+
// only this middleware writes, see sendFile
|
|
519
|
+
options._setHeaders = options.setHeaders;
|
|
481
520
|
// How long express.static remembers which twins a path has. A second is short enough that a
|
|
482
521
|
// deploy is picked up while it is still going out, and long enough that the lookup costs
|
|
483
522
|
// nothing under any traffic at all. { cache: false } asks the disk on every request.
|
|
484
523
|
let twinTtl = 0;
|
|
485
524
|
if (options.preCompressed) {
|
|
486
|
-
const cache =
|
|
487
|
-
typeof options.preCompressed === "object" ? options.preCompressed.cache : undefined
|
|
488
|
-
);
|
|
525
|
+
const cache = typeof options.preCompressed === "object" ? options.preCompressed.cache : undefined;
|
|
489
526
|
twinTtl =
|
|
490
527
|
cache === undefined
|
|
491
528
|
? 1000
|
|
492
529
|
: cache === false
|
|
493
530
|
? 0
|
|
494
531
|
: typeof cache === "string"
|
|
495
|
-
? ms(/** @type {
|
|
532
|
+
? ms(/** @type {import("ms").StringValue} */ (cache))
|
|
496
533
|
: cache;
|
|
497
534
|
if (typeof twinTtl !== "number" || !(twinTtl >= 0)) {
|
|
498
535
|
throw new TypeError("option preCompressed.cache must be a duration");
|
|
@@ -662,7 +699,7 @@ function serveStatic(root, options) {
|
|
|
662
699
|
// error handler with its errno, code, syscall and path still on it, and an
|
|
663
700
|
// error handler doing res.send(err) sends those as JSON. Passing the string
|
|
664
701
|
// sent an HTML page instead.
|
|
665
|
-
return next(asStatError(statError));
|
|
702
|
+
return next(asStatError(/** @type {HttpError} */ (statError)));
|
|
666
703
|
} else return next();
|
|
667
704
|
}
|
|
668
705
|
}
|
|
@@ -707,7 +744,7 @@ function serveStatic(root, options) {
|
|
|
707
744
|
res.status(404);
|
|
708
745
|
// the fs error, as above: the index file is missing and the error handler
|
|
709
746
|
// is told which one and where
|
|
710
|
-
return next(asStatError(err));
|
|
747
|
+
return next(asStatError(/** @type {HttpError} */ (err)));
|
|
711
748
|
} else return next();
|
|
712
749
|
}
|
|
713
750
|
if (found === null) {
|
|
@@ -778,7 +815,7 @@ function serveStatic(root, options) {
|
|
|
778
815
|
* encoding nobody knows throws, since decoding it wrong is worse than refusing.
|
|
779
816
|
*
|
|
780
817
|
* @param {string|undefined} contentEncoding
|
|
781
|
-
* @returns {
|
|
818
|
+
* @returns {Inflater|false|undefined}
|
|
782
819
|
*/
|
|
783
820
|
function createInflate(contentEncoding) {
|
|
784
821
|
const encoding = (contentEncoding || "identity").toLowerCase();
|
|
@@ -799,7 +836,7 @@ function createInflate(contentEncoding) {
|
|
|
799
836
|
return false;
|
|
800
837
|
}
|
|
801
838
|
// the flag the final flush passes, so a truncated stream errors instead of resolving empty
|
|
802
|
-
/** @type {
|
|
839
|
+
/** @type {Inflater} */ (stream)._finishFlag =
|
|
803
840
|
encoding === "br" ? zlib.constants.BROTLI_OPERATION_FINISH : zlib.constants.Z_FINISH;
|
|
804
841
|
return stream;
|
|
805
842
|
}
|
|
@@ -810,9 +847,9 @@ function createInflate(contentEncoding) {
|
|
|
810
847
|
* bytes over. They differ in the content type they claim and in what they turn the bytes into.
|
|
811
848
|
*
|
|
812
849
|
* @param {string} defaultType the type matched when the caller names none
|
|
813
|
-
* @param {
|
|
814
|
-
*
|
|
815
|
-
* @param {(options:
|
|
850
|
+
* @param {BodyHandler} beforeReturn turns the collected bytes into req.body. Called with the
|
|
851
|
+
* request, the response, next, the options, the body and its charset
|
|
852
|
+
* @param {(options: BodyParserOptions) => void} [checkOptions] whatever this parser alone has to check
|
|
816
853
|
* @param {string} [charsetPolicy] which charsets this parser accepts, as body-parser draws the
|
|
817
854
|
* lines: "utf" (json, utf-* only), "urlencoded" (utf-8 and iso-8859-1), "any" (anything iconv
|
|
818
855
|
* knows), or undefined for a parser that never decodes (raw)
|
|
@@ -972,7 +1009,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
972
1009
|
if (lengthNumber === 0) {
|
|
973
1010
|
req.bodyRead = true;
|
|
974
1011
|
const empty = Buffer.alloc(0);
|
|
975
|
-
if (!runVerify(req, res, next, options, empty)) {
|
|
1012
|
+
if (!runVerify(req, res, next, options, empty, encoding)) {
|
|
976
1013
|
return;
|
|
977
1014
|
}
|
|
978
1015
|
return beforeReturn(req, res, next, options, empty, encoding);
|
|
@@ -1002,6 +1039,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1002
1039
|
return next();
|
|
1003
1040
|
}
|
|
1004
1041
|
|
|
1042
|
+
/** @type {Inflater|false|undefined} */
|
|
1005
1043
|
let inflate;
|
|
1006
1044
|
let totalSize = 0;
|
|
1007
1045
|
const rawContentEncoding = req._rawHeader("content-encoding");
|
|
@@ -1070,7 +1108,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1070
1108
|
if (copyBody) {
|
|
1071
1109
|
buf = Buffer.from(buf);
|
|
1072
1110
|
}
|
|
1073
|
-
if (!runVerify(req, res, next, options, buf)) {
|
|
1111
|
+
if (!runVerify(req, res, next, options, buf, encoding)) {
|
|
1074
1112
|
return;
|
|
1075
1113
|
}
|
|
1076
1114
|
beforeReturn(req, res, next, options, buf, encoding);
|
|
@@ -1106,10 +1144,10 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1106
1144
|
* nothing, and an unhandled 'error' event ends the process. The listener goes on after
|
|
1107
1145
|
* the throw, since process() would have removed it.
|
|
1108
1146
|
*
|
|
1109
|
-
* @param {
|
|
1147
|
+
* @param {HttpError} err what inflate.process threw
|
|
1110
1148
|
*/
|
|
1111
1149
|
function failInflate(err) {
|
|
1112
|
-
/** @type {
|
|
1150
|
+
/** @type {Inflater} */ (inflate).instance?.on?.("error", () => {});
|
|
1113
1151
|
finished = true;
|
|
1114
1152
|
abs.length = 0;
|
|
1115
1153
|
target = null;
|
|
@@ -1162,7 +1200,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1162
1200
|
* finished flag matters: uWS goes on delivering chunks after an oversized body has
|
|
1163
1201
|
* been refused, and without it every further chunk would answer the request again.
|
|
1164
1202
|
*
|
|
1165
|
-
* @param {
|
|
1203
|
+
* @param {Buffer|ArrayBuffer} buf a Buffer, or an ArrayBuffer straight from uWS
|
|
1166
1204
|
*/
|
|
1167
1205
|
function onData(buf) {
|
|
1168
1206
|
if (finished) {
|
|
@@ -1177,7 +1215,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1177
1215
|
} catch (e) {
|
|
1178
1216
|
// a body that does not decompress is the client's mistake, and zlib
|
|
1179
1217
|
// throwing here used to escape into whatever called us
|
|
1180
|
-
return failInflate(e);
|
|
1218
|
+
return failInflate(/** @type {HttpError} */ (e));
|
|
1181
1219
|
}
|
|
1182
1220
|
}
|
|
1183
1221
|
|
|
@@ -1198,7 +1236,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1198
1236
|
try {
|
|
1199
1237
|
tail = inflate.process(EMPTY_BUFFER, inflate._finishFlag);
|
|
1200
1238
|
} catch (e) {
|
|
1201
|
-
return failInflate(e);
|
|
1239
|
+
return failInflate(/** @type {HttpError} */ (e));
|
|
1202
1240
|
}
|
|
1203
1241
|
if (tail.length && !keepChunk(tail)) {
|
|
1204
1242
|
return;
|
|
@@ -1226,7 +1264,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1226
1264
|
: abs.length === 1
|
|
1227
1265
|
? abs[0]
|
|
1228
1266
|
: Buffer.concat(abs);
|
|
1229
|
-
if (!runVerify(req, res, next, options, buf)) {
|
|
1267
|
+
if (!runVerify(req, res, next, options, buf, encoding)) {
|
|
1230
1268
|
return;
|
|
1231
1269
|
}
|
|
1232
1270
|
beforeReturn(req, res, next, options, buf, encoding);
|
|
@@ -1274,7 +1312,7 @@ const json = createBodyParser(
|
|
|
1274
1312
|
// either: it decodes through iconv, which strips it, so by the time the first character is
|
|
1275
1313
|
// looked at the mark is gone. JSON.parse would refuse it, so without this a body saved by
|
|
1276
1314
|
// an editor that writes a BOM is answered 400 here and 200 by Express.
|
|
1277
|
-
const text = stripBom(decodeBody(buf, encoding));
|
|
1315
|
+
const text = stripBom(decodeBody(buf, /** @type {string} */ (encoding)));
|
|
1278
1316
|
|
|
1279
1317
|
// "strict" means only an object or an array is a body, which is body-parser's default and
|
|
1280
1318
|
// was not honoured here at all: the check read req.body before this function had parsed
|
|
@@ -1302,7 +1340,7 @@ const json = createBodyParser(
|
|
|
1302
1340
|
} catch (e) {
|
|
1303
1341
|
// V8's own error, which is what body-parser hands on: its message says where the parse
|
|
1304
1342
|
// gave up, and it is still the SyntaxError an application may be testing for
|
|
1305
|
-
const err = /** @type {
|
|
1343
|
+
const err = /** @type {SyntaxError} */ (e);
|
|
1306
1344
|
return next(asBodyError(err, 400, "entity.parse.failed", { body: text }));
|
|
1307
1345
|
}
|
|
1308
1346
|
|
|
@@ -1329,7 +1367,7 @@ const text = createBodyParser(
|
|
|
1329
1367
|
"text/plain",
|
|
1330
1368
|
function (req, res, next, options, buf, encoding) {
|
|
1331
1369
|
try {
|
|
1332
|
-
req.body = decodeBody(buf, encoding);
|
|
1370
|
+
req.body = decodeBody(buf, /** @type {string} */ (encoding));
|
|
1333
1371
|
} catch (e) {
|
|
1334
1372
|
return next(e);
|
|
1335
1373
|
}
|
|
@@ -1371,7 +1409,7 @@ const urlencoded = createBodyParser(
|
|
|
1371
1409
|
"application/x-www-form-urlencoded",
|
|
1372
1410
|
function (req, res, next, options, buf, encoding) {
|
|
1373
1411
|
try {
|
|
1374
|
-
const body = decodeBody(buf, encoding);
|
|
1412
|
+
const body = decodeBody(buf, /** @type {string} */ (encoding));
|
|
1375
1413
|
// Express 5 defaults extended to false, so nested keys need opting in
|
|
1376
1414
|
const extended = typeof options.extended !== "undefined" ? options.extended : false;
|
|
1377
1415
|
// qs has to know the charset itself for anything but utf-8, and the sentinel options
|
|
@@ -1387,7 +1425,8 @@ const urlencoded = createBodyParser(
|
|
|
1387
1425
|
}
|
|
1388
1426
|
req.body = parsed;
|
|
1389
1427
|
} else {
|
|
1390
|
-
|
|
1428
|
+
// settled by the check below the parser, so it is a number by the time a body arrives
|
|
1429
|
+
const count = parameterCount(body, /** @type {number} */ (options.parameterLimit));
|
|
1391
1430
|
if (count === undefined) {
|
|
1392
1431
|
return next(bodyError("too many parameters", 413, "parameters.too.many"));
|
|
1393
1432
|
}
|
|
@@ -1401,7 +1440,7 @@ const urlencoded = createBodyParser(
|
|
|
1401
1440
|
arrayLimit: Math.max(100, count + 1),
|
|
1402
1441
|
charsetSentinel: options.charsetSentinel,
|
|
1403
1442
|
interpretNumericEntities: options.interpretNumericEntities,
|
|
1404
|
-
charset: encoding,
|
|
1443
|
+
charset: /** @type {"utf-8"|"iso-8859-1"} */ (encoding),
|
|
1405
1444
|
parameterLimit: options.parameterLimit
|
|
1406
1445
|
};
|
|
1407
1446
|
req.body = needsQs
|
|
@@ -1419,7 +1458,7 @@ const urlencoded = createBodyParser(
|
|
|
1419
1458
|
strictDepth: true,
|
|
1420
1459
|
charsetSentinel: options.charsetSentinel,
|
|
1421
1460
|
interpretNumericEntities: options.interpretNumericEntities,
|
|
1422
|
-
charset: encoding,
|
|
1461
|
+
charset: /** @type {"utf-8"|"iso-8859-1"} */ (encoding),
|
|
1423
1462
|
parameterLimit: options.parameterLimit
|
|
1424
1463
|
})
|
|
1425
1464
|
);
|
package/src/nest.js
CHANGED
|
@@ -47,7 +47,8 @@ const fulmine = require("./index.js");
|
|
|
47
47
|
*/
|
|
48
48
|
class FulmineExpressAdapter extends ExpressAdapter {
|
|
49
49
|
/**
|
|
50
|
-
* @param {
|
|
50
|
+
* @param {import("fulmine.js").FulmineApplication} [instance] an application from `fulmine()`; one is
|
|
51
|
+
* created when omitted
|
|
51
52
|
*/
|
|
52
53
|
constructor(instance) {
|
|
53
54
|
super(instance || fulmine());
|
|
@@ -61,7 +62,7 @@ class FulmineExpressAdapter extends ExpressAdapter {
|
|
|
61
62
|
/**
|
|
62
63
|
* The app is the server. Nest calls this once, from NestApplication's constructor.
|
|
63
64
|
*
|
|
64
|
-
* @param {
|
|
65
|
+
* @param {import("@nestjs/common").NestApplicationOptions} [options] the options NestFactory.create was given
|
|
65
66
|
* @returns {void}
|
|
66
67
|
*/
|
|
67
68
|
initHttpServer(options) {
|
package/src/node-shim.js
CHANGED
|
@@ -389,7 +389,7 @@ class NodeHttpResponse {
|
|
|
389
389
|
|
|
390
390
|
/**
|
|
391
391
|
* Whether these are node's own request and response rather than this project's.
|
|
392
|
-
* @param {
|
|
392
|
+
* @param {unknown} req anything a caller handed the router, which is the point of the check
|
|
393
393
|
*/
|
|
394
394
|
function isNodeRequest(req) {
|
|
395
395
|
return req instanceof IncomingMessage;
|
|
@@ -398,14 +398,19 @@ function isNodeRequest(req) {
|
|
|
398
398
|
/**
|
|
399
399
|
* Serves a request that arrived through node's HTTP server with the given router or app.
|
|
400
400
|
*
|
|
401
|
-
* @param {
|
|
401
|
+
* @param {import("./router.js")} router the router or application serving this request
|
|
402
402
|
* @param {import("http").IncomingMessage} nodeReq
|
|
403
403
|
* @param {import("http").ServerResponse} nodeRes
|
|
404
|
-
* @param {(err?:
|
|
404
|
+
* @param {(err?: unknown) => void} [next] called when nothing in the router answered
|
|
405
405
|
*/
|
|
406
406
|
function serveNodeRequest(router, nodeReq, nodeRes, next) {
|
|
407
|
-
|
|
408
|
-
const
|
|
407
|
+
// the shims stand in for uWS's pair, and the checker is told so once, here
|
|
408
|
+
const shimRes = /** @type {import("uWebSockets.js").HttpResponse} */ (
|
|
409
|
+
/** @type {unknown} */ (new NodeHttpResponse(nodeReq, nodeRes))
|
|
410
|
+
);
|
|
411
|
+
const shimReq = /** @type {import("uWebSockets.js").HttpRequest} */ (
|
|
412
|
+
/** @type {unknown} */ (new NodeHttpRequest(nodeReq))
|
|
413
|
+
);
|
|
409
414
|
const request = router.handleRequest(shimRes, shimReq);
|
|
410
415
|
const response = request.res;
|
|
411
416
|
// the shim's onAborted rides node's own close event, needed on every request here
|
package/src/optimizer.js
CHANGED
|
@@ -19,6 +19,7 @@ limitations under the License.
|
|
|
19
19
|
|
|
20
20
|
/** @typedef {import("./router.js")} Router */
|
|
21
21
|
/** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
|
|
22
|
+
/** @typedef {import("./application.js").Application} Application */
|
|
22
23
|
|
|
23
24
|
const {
|
|
24
25
|
patternToRegex,
|
|
@@ -53,7 +54,7 @@ const {
|
|
|
53
54
|
let Router;
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* @param {
|
|
57
|
+
* @param {typeof import("./router.js")} cls the Router class, passed in to keep this module out of its require cycle
|
|
57
58
|
*/
|
|
58
59
|
function useRouterClass(cls) {
|
|
59
60
|
Router = cls;
|
|
@@ -66,8 +67,8 @@ function useRouterClass(cls) {
|
|
|
66
67
|
*
|
|
67
68
|
* @param {Router} router
|
|
68
69
|
* @param {RouteEntry} route
|
|
69
|
-
* @param {
|
|
70
|
-
* @returns {
|
|
70
|
+
* @param {RouteEntry[]} routes every route of this router, in registration order
|
|
71
|
+
* @returns {RouteEntry[]|false} the chain, ending in the route itself
|
|
71
72
|
*/
|
|
72
73
|
function optimizeRoute(router, route, routes) {
|
|
73
74
|
const optimizedPath = [];
|
|
@@ -216,7 +217,8 @@ function optimizeRoute(router, route, routes) {
|
|
|
216
217
|
* routers and carrying their prefix down. Runs once, when the app starts listening, since it
|
|
217
218
|
* needs every route to have been registered first.
|
|
218
219
|
*
|
|
219
|
-
* @param {
|
|
220
|
+
* @param {Router} root the application whose routes are being compiled. A plain router, which has
|
|
221
|
+
* no uwsApp, is left alone
|
|
220
222
|
*/
|
|
221
223
|
function compileOptimizedRoutes(root) {
|
|
222
224
|
if (!root.uwsApp) {
|
|
@@ -376,7 +378,7 @@ function compileOptimizedRoutes(root) {
|
|
|
376
378
|
*
|
|
377
379
|
* @param {Router} router
|
|
378
380
|
* @param {RouteEntry} route
|
|
379
|
-
* @param {
|
|
381
|
+
* @param {RouteEntry[]} optimizedPath the routes to run, in order, ending with this one
|
|
380
382
|
*/
|
|
381
383
|
function registerUwsRoute(router, route, optimizedPath) {
|
|
382
384
|
let method = route.method.toLowerCase();
|
|
@@ -428,7 +430,7 @@ function registerUwsRoute(router, route, optimizedPath) {
|
|
|
428
430
|
// this one
|
|
429
431
|
if (caseGuards !== null && anyGuardHits(caseGuards, req.getUrl())) {
|
|
430
432
|
// an application is what registers native routes, and only it serves
|
|
431
|
-
return /** @type {
|
|
433
|
+
return /** @type {Application} */ (router)._serveGeneric(res, req);
|
|
432
434
|
}
|
|
433
435
|
const request = router.handleRequest(res, req, preset, skipHolder);
|
|
434
436
|
const response = request.res;
|
|
@@ -526,7 +528,7 @@ function registerUwsRoute(router, route, optimizedPath) {
|
|
|
526
528
|
|
|
527
529
|
// the response prototype the route will really run under: its own app's, which sees a
|
|
528
530
|
// method patched there or inherited from a parent app, falling back to the registering app
|
|
529
|
-
const responseProto =
|
|
531
|
+
const responseProto = route.owner?.response ?? /** @type {Application} */ (router).response;
|
|
530
532
|
// check if route is declarative
|
|
531
533
|
if (
|
|
532
534
|
optimizedPath.length === 1 && // must not have middlewares
|
package/src/options.d.ts
CHANGED
|
@@ -42,8 +42,8 @@ export interface SendFileOptions {
|
|
|
42
42
|
dotfiles?: "allow" | "deny" | "ignore" | "ignore_files";
|
|
43
43
|
/** Extra headers for the response. */
|
|
44
44
|
headers?: Record<string, string>;
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/** Internal: express.static's setHeaders, which res.sendFile itself does not take. */
|
|
46
|
+
_setHeaders?: (res: any, path: string, stat: any) => void;
|
|
47
47
|
/** First byte of the window to send. */
|
|
48
48
|
start?: number;
|
|
49
49
|
/** Last byte of the window to send. */
|
|
@@ -68,6 +68,8 @@ export interface StaticOptions extends SendFileOptions {
|
|
|
68
68
|
fallthrough?: boolean;
|
|
69
69
|
/** Extensions tried when the path names no file, or false to try none. */
|
|
70
70
|
extensions?: string[] | false;
|
|
71
|
+
/** Called before the file goes out, to set headers from the path or its stat. */
|
|
72
|
+
setHeaders?: (res: any, path: string, stat: any) => void;
|
|
71
73
|
/**
|
|
72
74
|
* Serve `file.br` or `file.gz` in place of `file` when one is on disk and the client takes it.
|
|
73
75
|
* Off by default. Vary: Accept-Encoding is sent whether or not a variant is found, and the
|
|
@@ -95,8 +97,11 @@ export interface BodyParserOptions {
|
|
|
95
97
|
limit?: number | string;
|
|
96
98
|
/** Which content types this parser claims. */
|
|
97
99
|
type?: string | string[] | ((req: any) => boolean);
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Runs on the raw bytes before parsing, which is where a signature check belongs. The charset
|
|
102
|
+
* is the one the body is decoded with, null for raw, as body-parser hands it over.
|
|
103
|
+
*/
|
|
104
|
+
verify?: false | ((req: any, res: any, buf: Buffer, encoding: string | null) => void);
|
|
100
105
|
/** Whether a compressed body is decompressed rather than refused. */
|
|
101
106
|
inflate?: boolean;
|
|
102
107
|
/** The charset assumed when the request names none. */
|
package/src/request-utils.js
CHANGED
|
@@ -23,7 +23,8 @@ const { isIP } = require("node:net");
|
|
|
23
23
|
|
|
24
24
|
// accepts, type-is, proxy-addr and fresh declare a node IncomingMessage but read only .headers off
|
|
25
25
|
// it. This request is not one, so it is passed as itself and the declared type is stepped around.
|
|
26
|
-
|
|
26
|
+
/** @param {Request} req @returns {import("http").IncomingMessage} */
|
|
27
|
+
const asMessage = (req) => /** @type {import("http").IncomingMessage} */ (/** @type {unknown} */ (req));
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Writes an address like node's socket.remoteAddress, which is inet_ntop and so RFC 5952: leading
|
|
@@ -106,7 +107,7 @@ function isMappedIPv4(bytes) {
|
|
|
106
107
|
* whenever the listener is dual stack, which is every listen() without an IPv4 address. uWS already
|
|
107
108
|
* gives mapped peers as sixteen bytes, four bytes come only from a v4 listener or the node shim.
|
|
108
109
|
*
|
|
109
|
-
* @param {
|
|
110
|
+
* @param {import("./application.js").Application} app the application the request arrived at
|
|
110
111
|
* @returns {boolean}
|
|
111
112
|
*/
|
|
112
113
|
function mapsIPv4Peer(app) {
|