fulmine.js 5.19.2 → 5.19.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.
@@ -0,0 +1,950 @@
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+ Copyright 2026 Nigro Simone
4
+
5
+ This file is derived from Ultimate Express and has been modified.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+ */
19
+
20
+ const { getPatternMeta, pathsCanOverlap, regexpGroupKeys, EMPTY_REGEX } = require("./utils.js");
21
+ const Response = require("./response.js");
22
+ const Request = require("./request.js");
23
+ const { METHODS } = require("http");
24
+
25
+ /** @typedef {import("./walk.js")} Walk */
26
+ /** @typedef {import("./router.js")} Router */
27
+ /**
28
+ * One entry of a router's table, as createRoute in router.js builds it. Left loose on purpose: the
29
+ * optimizer hangs half a dozen more fields on it after registration, and writing them all out here
30
+ * would be a second copy of createRoute that nothing keeps in step.
31
+ * @typedef {any} RouteEntry
32
+ */
33
+
34
+ // whether a registered path could be asked for in another case, which is what decides whether the
35
+ // native router can be trusted to prefer it, see _optimizeRoute
36
+ const HAS_LETTER = /[a-zA-Z]/;
37
+
38
+ /**
39
+ * Whether an earlier route would have answered this path had case not mattered. A guard is a
40
+ * folded string when the earlier path is a literal, and an insensitive pattern when it has
41
+ * parameters of its own.
42
+ *
43
+ * The string side is compared character by character rather than through toLowerCase: it sits on
44
+ * the hot path of every parameter route with an earlier literal, and saying no must allocate
45
+ * nothing.
46
+ *
47
+ * @param {(string|RegExp)[]} guards
48
+ * @param {string} path the path as it arrived
49
+ * @returns {boolean}
50
+ */
51
+ function anyGuardHits(guards, path) {
52
+ for (let i = 0; i < guards.length; i++) {
53
+ const guard = guards[i];
54
+ if (typeof guard !== "string") {
55
+ if (guard.test(path)) {
56
+ return true;
57
+ }
58
+ continue;
59
+ }
60
+ // the guard is a registered path, which under the default routing answers the same path
61
+ // with one trailing slash too: "/x1" registered serves "/x1/", so "/X1/" is as much a case
62
+ // variant of it as "/X1". Missing that answered "/X1/" from the parameter route behind it
63
+ // while express answered from the literal. The regex guards are built non-strict and
64
+ // already accept it. Erring wide costs nothing: a guard that hits only hands the request
65
+ // to the generic router
66
+ const slashed = path.length === guard.length + 1 && path.charCodeAt(guard.length) === 0x2f;
67
+ if (guard.length !== path.length && !slashed) {
68
+ continue;
69
+ }
70
+ let same = true;
71
+ for (let j = 0; j < guard.length; j++) {
72
+ let code = path.charCodeAt(j);
73
+ // A to Z only, which is the fold express's insensitive routing does
74
+ if (code >= 65 && code <= 90) {
75
+ code += 32;
76
+ }
77
+ if (code !== guard.charCodeAt(j)) {
78
+ same = false;
79
+ break;
80
+ }
81
+ }
82
+ if (same) {
83
+ return true;
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+
89
+ // every method the declarative compiler can emit: a patched one must disable compilation, or the
90
+ // patch would be honoured everywhere but on compiled routes
91
+ const resCodes = {},
92
+ resDecMethods = ["set", "setHeader", "header", "send", "end", "append", "status", "json", "sendStatus"];
93
+ for (const method of resDecMethods) {
94
+ resCodes[method] = Response.prototype[method].toString();
95
+ }
96
+
97
+ /**
98
+ * The layer Express makes for one mounted handler. `name` is what a caller matches on: a function's
99
+ * own name, "router" for a mounted router, and "<anonymous>" for the rest, exactly as express reads
100
+ * them off the handle.
101
+ *
102
+ * @param {RouteEntry} route
103
+ * @param {any} callback a handler, or a mounted router, which is callable and carries _routes
104
+ * @returns {any} the layer object, which is express's shape and not one of ours
105
+ */
106
+ function layerFor(route, callback) {
107
+ const layer = {
108
+ handle: callback,
109
+ // express reads the name off the handle, and its own handles are named: a mounted
110
+ // application is "app" and a mounted router "router", whatever this project happens
111
+ // to call the function underneath
112
+ name: Array.isArray(callback._routes)
113
+ ? callback._isApplication
114
+ ? "app"
115
+ : "router"
116
+ : callback.name || "<anonymous>",
117
+ params: undefined,
118
+ path: undefined,
119
+ keys: [],
120
+ route: undefined
121
+ };
122
+ route._layers.set(callback, layer);
123
+ return layer;
124
+ }
125
+
126
+ /**
127
+ * The layer Express makes for a route, whose handle runs the route's own handlers one after
128
+ * another. Express calls that handle `handle`, and a caller that looks for a route layer looks for
129
+ * that name.
130
+ *
131
+ * @param {RouteEntry} route
132
+ * @returns {any} the layer object, which is express's shape and not one of ours
133
+ */
134
+ function routeLayer(route) {
135
+ const handle = function handle(req, res, next) {
136
+ let index = 0;
137
+ const step = (err) => {
138
+ const callback = route.callbacks[index++];
139
+ if (callback === undefined) {
140
+ return next(err);
141
+ }
142
+ const isErrorHandler = callback.length === 4;
143
+ if ((err === undefined || err === null) === isErrorHandler) {
144
+ return step(err);
145
+ }
146
+ try {
147
+ return isErrorHandler ? callback(err, req, res, step) : callback(req, res, step);
148
+ } catch (thrown) {
149
+ return step(thrown);
150
+ }
151
+ };
152
+ step();
153
+ };
154
+ return { handle, name: "handle", params: undefined, path: undefined, keys: [], route: route.exposed };
155
+ }
156
+
157
+ /**
158
+ * The native handler's resolve, invoked as this.resolve(matched) with the walk as receiver. The
159
+ * promise pair _routeRequest allocates exists for callers that await; the uWS handler never did,
160
+ * and on the common path that promise never even settled: an async frame and two promises of
161
+ * floating garbage per request.
162
+ *
163
+ * The 404 epilogue stays on a microtask, where the await used to resume: a middleware that writes
164
+ * after calling next() must still win the headersSent check, as it does in express.
165
+ * @this {Walk}
166
+ */
167
+ function nativeDone(matched) {
168
+ if (this.settled) {
169
+ return;
170
+ }
171
+ this.settled = true;
172
+ if (!matched) {
173
+ queueMicrotask(() => {
174
+ const response = this.res;
175
+ if (response.headersSent || response.aborted) {
176
+ return;
177
+ }
178
+ try {
179
+ this.router._endUnmatched(this.req, response);
180
+ } catch (err) {
181
+ if (response.aborted || response.finished) {
182
+ logError(this.router, err);
183
+ } else {
184
+ this.router._handleError(err, null, this.req, response);
185
+ }
186
+ }
187
+ });
188
+ }
189
+ }
190
+
191
+ /**
192
+ * The native handler's reject: answers 500 as express's final handler would, instead of dying as
193
+ * an unhandled rejection. Deferred like the resolve, since every rejection used to reach the
194
+ * handler's catch through an await.
195
+ * @this {Walk}
196
+ */
197
+ function nativeFail(err) {
198
+ if (this.settled) {
199
+ return;
200
+ }
201
+ this.settled = true;
202
+ queueMicrotask(() => {
203
+ const response = this.res;
204
+ if (response.aborted || response.finished) {
205
+ logError(this.router, err);
206
+ } else {
207
+ this.router._handleError(err, null, this.req, response);
208
+ }
209
+ });
210
+ }
211
+
212
+ /**
213
+ * How much of the path a mount takes, which is what its own pattern matched and never more than
214
+ * there is. Exec runs on the same fixed-up path _pathMatches tested: a parent mount that consumed
215
+ * everything leaves "", where the pattern was matched against "/".
216
+ *
217
+ * Counting what each mount took, rather than composing one pattern out of the whole stack, is the
218
+ * difference between a sum and a guess: a mount written as an optional group composes into a
219
+ * pattern the path no longer satisfies, and the prefix stayed on.
220
+ *
221
+ * @param {RouteEntry} route
222
+ * @param {Request} req
223
+ * @returns {number}
224
+ */
225
+ function mountPrefixLength(route, req) {
226
+ // a use with no path is EMPTY_REGEX, which matches "" at 0 whatever the path is. Answered
227
+ // without the exec, since this runs per hop and most middleware is pathless
228
+ if (route.pattern === EMPTY_REGEX) {
229
+ return 0;
230
+ }
231
+ // the registration-time constant of a literal mount, exec-free. See createRoute
232
+ if (route.mountLen !== undefined) {
233
+ return route.mountLen;
234
+ }
235
+ if (typeof route.pattern === "string") {
236
+ return route.pattern.length;
237
+ }
238
+ const path = req._opPath;
239
+ const matched = route.pattern.exec(path === "" ? "/" : path);
240
+ return matched ? Math.min(matched[0].length, path.length) : 0;
241
+ }
242
+
243
+ /**
244
+ * Writes the path the routes below a mount see: the original with what the mounts took off the
245
+ * front. The root reads as "/" rather than as nothing, which is how express hands it over.
246
+ *
247
+ * @param {Request} req
248
+ */
249
+ function setMountedPath(req) {
250
+ req._opPath = req._consumed === 0 ? req._originalPath : req._originalPath.slice(req._consumed);
251
+ req._opPathLower = null;
252
+ req.url = req._opPath === "" ? "/" + req.urlQuery : req._opPath + req.urlQuery;
253
+ req._path = req._opPath === "" ? "/" : req._opPath;
254
+ req._lastUrl = req.url;
255
+ }
256
+
257
+ // req.path as the request class declares it, taken off the prototype rather than written out a
258
+ // second time. A request the router adopts is a plain object and gets it defined on itself, see
259
+ // adoptPlainRequest. Enumerable, as express's own is.
260
+ const PATH_PROPERTY = {
261
+ .../** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Request.prototype, "path")),
262
+ enumerable: true
263
+ };
264
+
265
+ // and the two the walk calls when a middleware rewrote req.url or req.method, for the same reason:
266
+ // an adopted request has no prototype of ours to find them on, and a rewrite through one of those
267
+ // routers threw instead of being taken over
268
+ const ABSORB_URL = Request.prototype._absorbUrlRewrite;
269
+ const ABSORB_METHOD = Request.prototype._absorbMethodRewrite;
270
+
271
+ const NO_PARAM_NAMES = [];
272
+
273
+ /**
274
+ * The parameter names a route captures with its own pattern.
275
+ *
276
+ * This is the set express runs param callbacks for. A name that reached req.params from a mount
277
+ * above, through mergeParams, belongs to that mount's router: express walks the keys the layer
278
+ * itself matched. Reading req.params instead ran a callback for every inherited name too, and
279
+ * turned a 200 into a 500 when one of them refused the value.
280
+ *
281
+ * Worked out once per route and kept, since it follows from the pattern.
282
+ *
283
+ * @param {RouteEntry} route
284
+ * @returns {string[]}
285
+ */
286
+ function ownParamNames(route) {
287
+ let names = route._ownParamNames;
288
+ if (names !== undefined) {
289
+ return names;
290
+ }
291
+ if (route.optimizedParams) {
292
+ // µWS matched the pattern and hands the values back by position, under these names
293
+ names = route.optimizedParams;
294
+ } else if (route.pattern instanceof RegExp) {
295
+ const meta = getPatternMeta(route.pattern);
296
+ // outputNames is what _extractParams writes into params; a RegExp the application wrote
297
+ // itself was never compiled here, so its capture groups are the names
298
+ names = meta ? meta.outputNames : regexpGroupKeys(route.pattern);
299
+ } else {
300
+ names = NO_PARAM_NAMES;
301
+ }
302
+ route._ownParamNames = names;
303
+ return names;
304
+ }
305
+
306
+ /**
307
+ * Whether this route reads the parameters of the mounts above it, which is its own router asking
308
+ * for them. The stack holds what a mergeParams router captured on the way in, and a plain router
309
+ * mounted inside one must not read it: express asks each router in turn, not the outermost.
310
+ *
311
+ * @param {RouteEntry} route
312
+ * @param {Router} fallback the router dispatching, when the route names no owner
313
+ * @returns {boolean}
314
+ */
315
+ function mergesParams(route, fallback) {
316
+ const owner = route.owner ?? fallback;
317
+ return Boolean(owner?._settings?.mergeParams);
318
+ }
319
+
320
+ // shared empty candidate list, so _scanFrom never tests for a missing map entry twice
321
+ const EMPTY_INDICES = /** @type {number[]} */ ([]);
322
+
323
+ /**
324
+ * The generic scan's index over a router's literal routes: route positions by folded pattern, so
325
+ * a scan visits the routes registered for this exact path instead of comparing every one. String
326
+ * patterns are pure literals, everything else, "/*" included, stays in alwaysVisit and is still
327
+ * matched per request by _pathMatches.
328
+ *
329
+ * @param {any[]} routes the router's own table
330
+ * @param {boolean} caseFlag the frozen case-sensitivity flag
331
+ * @returns {{map: Map<string, number[]>, alwaysVisit: number[]}}
332
+ */
333
+ function buildLiteralIndex(routes, caseFlag) {
334
+ const map = new Map();
335
+ const alwaysVisit = [];
336
+ for (let i = 0; i < routes.length; i++) {
337
+ const pattern = routes[i].pattern;
338
+ if (typeof pattern === "string" && pattern !== "/*") {
339
+ const key = caseFlag ? pattern : routes[i].patternLower;
340
+ const list = map.get(key);
341
+ if (list === undefined) {
342
+ map.set(key, [i]);
343
+ } else {
344
+ list.push(i);
345
+ }
346
+ } else {
347
+ alwaysVisit.push(i);
348
+ }
349
+ }
350
+ return { map, alwaysVisit };
351
+ }
352
+
353
+ /**
354
+ * The position of the first value >= from in an ascending list, which is list.length when there
355
+ * is none: where a scan resuming at `from` enters a candidate list.
356
+ *
357
+ * @param {number[]} list
358
+ * @param {number} from
359
+ * @returns {number}
360
+ */
361
+ function firstAtLeast(list, from) {
362
+ let low = 0;
363
+ let high = list.length;
364
+ while (low < high) {
365
+ const mid = (low + high) >> 1;
366
+ if (list[mid] < from) {
367
+ low = mid + 1;
368
+ } else {
369
+ high = mid;
370
+ }
371
+ }
372
+ return low;
373
+ }
374
+
375
+ /**
376
+ * The route's own params merged with those of the mounts it sits under, in express's order: an
377
+ * outer mount first, the route's own last. Numbered captures do not overwrite each other, they
378
+ * shift, so a RegExp mount capturing one group leaves the route's own group numbered from one.
379
+ *
380
+ * @param {Record<string, any>} own what this route's own pattern captured
381
+ * @param {Record<string, any>[]} stack the mounts, outermost first
382
+ * @returns {Record<string, any>}
383
+ */
384
+ function mergeParams(own, stack) {
385
+ const merged = Object.create(null);
386
+ for (const params of stack) {
387
+ Object.assign(merged, params);
388
+ }
389
+ // both sides numbering from zero means the outer ones keep their places and these move up
390
+ if (own[0] !== undefined && merged[0] !== undefined) {
391
+ let count = 0;
392
+ while (merged[count] !== undefined) {
393
+ count++;
394
+ }
395
+ let last = 0;
396
+ while (own[last] !== undefined) {
397
+ last++;
398
+ }
399
+ for (last--; last >= 0; last--) {
400
+ own[last + count] = own[last];
401
+ if (last < count) {
402
+ delete own[last];
403
+ }
404
+ }
405
+ }
406
+ return Object.assign(merged, own);
407
+ }
408
+
409
+ /**
410
+ * The scheme and authority of an absolute request target, or "" for the ordinary kind.
411
+ *
412
+ * A request line may carry the whole URI, and express matches on the path while leaving req.url as
413
+ * it arrived. Same rule it uses: a "://" before any "?" means everything up to the slash after it
414
+ * is not path.
415
+ *
416
+ * @param {string} url
417
+ * @returns {string}
418
+ */
419
+ function protohostOf(url) {
420
+ if (url.length === 0 || url.charCodeAt(0) === 0x2f) {
421
+ return "";
422
+ }
423
+ const searchIndex = url.indexOf("?");
424
+ const pathLength = searchIndex === -1 ? url.length : searchIndex;
425
+ const fqdnIndex = url.slice(0, pathLength).indexOf("://");
426
+ if (fqdnIndex === -1) {
427
+ return "";
428
+ }
429
+ const slash = url.indexOf("/", fqdnIndex + 3);
430
+ return slash === -1 ? url : url.slice(0, slash);
431
+ }
432
+
433
+ /**
434
+ * Fills in what dispatch reads on a request that did not come from uWS.
435
+ *
436
+ * express's router can be driven with a plain object, `router.handle({ url, method }, res, next)`,
437
+ * and its own tests do exactly that. Only ever called for such a request: one of ours arrives with
438
+ * these fields already set.
439
+ *
440
+ * req.url becomes an accessor, so the router goes on writing plain paths to it while a reader sees
441
+ * the absolute URI it arrived as, which keeps the protohost out of the dispatch.
442
+ *
443
+ * @param {any} req the plain object a caller drove the router with, not one of our requests
444
+ * @param {Router} router
445
+ */
446
+ function adoptPlainRequest(req, router) {
447
+ const arrived = typeof req.url === "string" ? req.url : "";
448
+ const protohost = protohostOf(arrived);
449
+ let raw = arrived.slice(protohost.length);
450
+ if (protohost !== "") {
451
+ Object.defineProperty(req, "url", {
452
+ configurable: true,
453
+ enumerable: true,
454
+ get() {
455
+ return protohost + raw;
456
+ },
457
+ set(value) {
458
+ const written = String(value);
459
+ raw = written.startsWith(protohost) ? written.slice(protohost.length) : written;
460
+ }
461
+ });
462
+ }
463
+
464
+ const queryIndex = raw.indexOf("?");
465
+ const path = queryIndex === -1 ? raw : raw.slice(0, queryIndex);
466
+ req.urlQuery = queryIndex === -1 ? "" : raw.slice(queryIndex);
467
+ req._rawQuery = req.urlQuery.slice(1);
468
+ req._path = path;
469
+ // an adopted request is a plain object, so it carries no prototype of ours and reads its path
470
+ // off a property of its own. The class's getter itself, so there is one of it
471
+ Object.defineProperty(req, "path", PATH_PROPERTY);
472
+ req._absorbUrlRewrite = ABSORB_URL;
473
+ req._absorbMethodRewrite = ABSORB_METHOD;
474
+ req.originalUrl = req.originalUrl ?? arrived;
475
+ req._originalPath = path;
476
+ req.endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
477
+ req._opPath = path;
478
+ req._opPathLower = null;
479
+ req._mayFailDecode = null;
480
+ req._lastUrl = req.url;
481
+ req._lastMethod = req.method;
482
+ req._isOptions = req.method === "OPTIONS";
483
+ req._isHead = req.method === "HEAD";
484
+ req.params = req.params ?? Object.create(null);
485
+ // null, not fresh arrays: the push sites materialize them on the first mount, and most
486
+ // requests never see one, same as the Request constructor
487
+ req._stack = null;
488
+ req._consumed = 0;
489
+ req._mountSlash = false;
490
+ req._paramStack = null;
491
+ req._matchedMethods = req._isOptions ? new Set() : null;
492
+ req.routeCount = 1;
493
+ // read when a mount is left, and there is no application here to read it from
494
+ req.app = req.app ?? router;
495
+ }
496
+
497
+ /**
498
+ * What express's logerror does. Its final handler prints the error it is about to answer with,
499
+ * unless the application runs under `env: "test"`, which is how its own suite stays quiet, and it
500
+ * prints the stack rather than the object. A falsy throw is not printed at all, since finalhandler
501
+ * only calls onerror when there is an error to call it with.
502
+ *
503
+ * @param {Router} router the router whose settings decide it
504
+ * @param {any} err whatever was thrown, which need not be an Error
505
+ * @returns {void}
506
+ */
507
+ function logError(router, err) {
508
+ if (err && router.get("env") !== "test") {
509
+ console.error(err.stack || err.toString());
510
+ }
511
+ }
512
+
513
+ /**
514
+ * The uWS onAborted handler, bound to the response: a closure here captured two locals and cost
515
+ * a context plus a function per request, for a path that only ever runs on a client abort.
516
+ * @this {any} the response, with the request linked as this.req
517
+ */
518
+ function onNativeAborted() {
519
+ const response = this;
520
+ const request = response.req;
521
+ // node's wording for a client abort, which is what body consumers match on
522
+ /** @type {NodeJS.ErrnoException} */
523
+ const err = new Error("aborted");
524
+ err.code = "ECONNRESET";
525
+ response.aborted = true;
526
+ response.finished = true;
527
+ // node's order on the request: 'aborted', then the stream dies, then 'close'. The
528
+ // error goes only to whoever listens for it, since a destroy(err) with no listener
529
+ // would take down the process
530
+ request.emit("aborted");
531
+ // and the response dies between the two, which is where node puts it. Destroyed rather than
532
+ // told to emit 'close', because being destroyed is the state express is in here and the rest
533
+ // follows from it: 'close' goes out once, a later res.write returns false and calls back with
534
+ // ERR_STREAM_DESTROYED, and no 'error' is emitted.
535
+ //
536
+ // Without this a handler learnt about the abort only from a write failing, so one that had sent
537
+ // its head and gone quiet never learnt at all. `res.on("close")` is where cancellation hangs in
538
+ // every proxy and every streaming endpoint
539
+ response.destroy();
540
+ request.destroy(request.listenerCount("error") > 0 ? err : undefined);
541
+ response.socket?.emit("error", err);
542
+ }
543
+
544
+ /**
545
+ * The per-request constants of a fully literal native registration. µWS matched the URL byte for
546
+ * byte against this exact pattern and dispatches by method, so the request constructor can take
547
+ * these as given instead of asking uWS and recomputing them on every request.
548
+ *
549
+ * @param {string} path the registered pattern, which is what getUrl() would have answered
550
+ * @param {string} method uppercase, fixed by which uWS verb the registration used
551
+ */
552
+ function nativePreset(path, method) {
553
+ const endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
554
+ return {
555
+ path,
556
+ method,
557
+ endsWithSlash,
558
+ opPath: path,
559
+ isOptions: method === "OPTIONS",
560
+ isHead: method === "HEAD",
561
+ // set at registration when the whole chain provably never reads a header, or never
562
+ // reads the query; mutable, because a middleware added after listen takes them back
563
+ skipHeaders: false,
564
+ skipQuery: false
565
+ };
566
+ }
567
+
568
+ /**
569
+ * Whether any error middleware exists anywhere under this router, mounted routers and sub-apps
570
+ * included. The header-skip analysis needs the answer to be no: a throw inside an analyzed
571
+ * handler would hand the request to code nobody analyzed.
572
+ *
573
+ * @param {Router} router
574
+ * @returns {boolean}
575
+ */
576
+ function hasErrorMiddleware(router) {
577
+ for (const route of router._routes) {
578
+ for (const callback of route.callbacks) {
579
+ // a mounted router or a callable sub-app carries routes of its own; the callable
580
+ // app is also a function, so the routes are looked for first
581
+ if (callback && callback._routes) {
582
+ if (hasErrorMiddleware(callback)) {
583
+ return true;
584
+ }
585
+ } else if (typeof callback === "function" && callback.length >= 4) {
586
+ return true;
587
+ }
588
+ }
589
+ }
590
+ return false;
591
+ }
592
+
593
+ /**
594
+ *
595
+ */
596
+ function checkHandlers(handlers, emptyMessage = "argument handler is required") {
597
+ if (handlers.length === 0) {
598
+ throw new TypeError(emptyMessage);
599
+ }
600
+ for (const handler of handlers) {
601
+ if (typeof handler !== "function") {
602
+ throw new TypeError("argument handler must be a function");
603
+ }
604
+ }
605
+ }
606
+
607
+ // what a route's callback is, so that a hop reads a number instead of asking instanceof and length
608
+ const CALLBACK_PLAIN = 0;
609
+ const CALLBACK_ERROR = 1;
610
+ const CALLBACK_ROUTER = 2;
611
+
612
+ /**
613
+ * Reports a parameter that will not decode, unless something is already being reported.
614
+ *
615
+ * Matching a route decodes its parameters, and that happens while the walk is still looking for
616
+ * whoever should answer. Express keeps the first error it has: `layerError = layerError || match`
617
+ * in its router. Overwriting meant express.static's Bad Request on an escape it could not decode
618
+ * was replaced by the decode failure of a route further down that was never going to run. Found by
619
+ * fuzzing route tables against express.
620
+ *
621
+ * @param {Request} req
622
+ * @param {RouteEntry} route
623
+ * @param {any} err whatever decoding threw
624
+ */
625
+ function raiseDecodeFailure(req, route, err) {
626
+ if (req._error) {
627
+ return;
628
+ }
629
+ req._error = err;
630
+ req._errorKey = route.routeKey;
631
+ req._errorGroup = route.group;
632
+ }
633
+
634
+ // the verbs a body is read for unless the application says otherwise, which is the parsers' own
635
+ // list. A request with any other verb reaches a parser's method check and leaves through it
636
+ const BODY_METHODS = new Set(["POST", "PUT", "PATCH", "QUERY"]);
637
+
638
+ /**
639
+ * Whether this layer can be stepped over for this request without changing a thing.
640
+ *
641
+ * Only the body parsers are ever asked. Their prologue leaves a request that said nothing about a
642
+ * body alone, whatever content type it carries, which is what `kGetSafe` records. Two conditions
643
+ * on top of that mark, and both are needed:
644
+ *
645
+ * The request must have said nothing about framing at all, a `content-length: 0` included. A parser
646
+ * that can see a length answers about the body it describes even when that body is empty: a zero
647
+ * length with a charset nobody can decode is a 415, in express and here.
648
+ *
649
+ * And the verb must be one no parser reads a body for. With no length and no transfer-encoding a
650
+ * POST still walks into the read, comes back with nothing, and leaves `req.body` as the empty value
651
+ * its parser produces, which a handler can see.
652
+ *
653
+ * What it is worth: a hop measured 367us per thousand requests and the parser prologue it reaches
654
+ * measured 38, ten to one for a layer that had nothing to do.
655
+ *
656
+ * That number is why fusing consecutive layers into one generated function keeps coming up, and why
657
+ * it is not here. Counted over a real front (morgan, helmet, compression, cors, the two body
658
+ * parsers, express-session, one of one's own, express.static): three of the nine can be fused, and
659
+ * the longest run of fusable ones in a row is one, where fusing needs two. The six that fail all
660
+ * call next from inside a callback, reading a body, stat-ing a file, loading a session, so no
661
+ * design that keeps the semantics can fuse them.
662
+ *
663
+ * @param {RouteEntry} route
664
+ * @param {Request} req
665
+ * @returns {boolean}
666
+ */
667
+ function stepsOver(route, req) {
668
+ if (route.bodyParserOnly !== true || req._hasBodyHeaders === true) {
669
+ return false;
670
+ }
671
+ if (BODY_METHODS.has(req.method)) {
672
+ return false;
673
+ }
674
+ // an application can add its own. Read once and kept, which is what the parser behind this
675
+ // layer does with the same setting: asking on every request measured 17 microseconds per
676
+ // thousand, a third of what stepping over the layer saves
677
+ if (route.bodyMethods === undefined) {
678
+ route.bodyMethods = req.app.get("body methods") ?? null;
679
+ }
680
+ if (route.bodyMethods !== null && route.bodyMethods.includes(req.method)) {
681
+ return false;
682
+ }
683
+ // The layer is not entered, so it leaves the one mark it would have left: the parser puts
684
+ // `body` on the request before it works out that there is nothing to read. A library asks
685
+ // `"body" in req` to tell "a parser has run" from "none has", and a skip that did not leave
686
+ // it would answer a GET differently from express. See the same seeding in middlewares.js
687
+ if (!("body" in req)) {
688
+ // cast because `body` is deliberately not a field of Request, see the comment there
689
+ /** @type {any} */ (req).body = undefined;
690
+ }
691
+ return true;
692
+ }
693
+
694
+ /**
695
+ * Whether a route could answer a request for this path, judged on the pattern it was compiled to.
696
+ * A literal answers only itself; anything with a parameter or a wildcard answers what its regex
697
+ * says. Used where the question is "would this earlier route have had its turn first".
698
+ *
699
+ * @param {RouteEntry} route
700
+ * @param {string} path
701
+ * @returns {boolean}
702
+ */
703
+ function couldAnswer(route, path) {
704
+ if (route.pattern instanceof RegExp) {
705
+ return route.pattern.test(path);
706
+ }
707
+ return route.pattern === path;
708
+ }
709
+
710
+ /**
711
+ * Whether a layer written before a mount could answer a request for one of the paths inside it.
712
+ *
713
+ * A mount covers everything under its path, so this is a question about a subtree and not about the
714
+ * mount point: `/a` and `/:p0/:p1/:p2` match none of each other's text and both answer `/a/x/y`.
715
+ * uWS jumps straight to whichever leaf it registered, so a leaf a layer like this could have
716
+ * answered has to stay on the generic path.
717
+ *
718
+ * Only layers with more segments than the mount path reach this: one with as few already matches
719
+ * the mount point itself. Compared folded whichever way the routers are set, and a wrong yes costs
720
+ * a leaf its native registration and nothing else.
721
+ *
722
+ * @param {{path: string, use: boolean, method: string, all: boolean}} guard
723
+ * @param {string} leafPath the leaf's absolute path, parameters and all
724
+ * @param {RouteEntry} leaf
725
+ * @returns {boolean}
726
+ */
727
+ function shadowsLeaf(guard, leafPath, leaf) {
728
+ if (!guard.all && guard.method !== leaf.method && !(guard.method === "HEAD" && leaf.method === "GET")) {
729
+ return false;
730
+ }
731
+ return pathsCanOverlap(guard.path.toLowerCase(), leafPath.toLowerCase(), guard.use);
732
+ }
733
+
734
+ /**
735
+ * The layers before a mount that answer some of what is inside it and not all of it, the one thing
736
+ * neither the chain nor uWS's own choice can say: the chain runs what is in it without matching
737
+ * again, and uWS picks by specificity. They are carried down the walk and asked about every leaf,
738
+ * see shadowsLeaf.
739
+ *
740
+ * @param {Router} router the router the mount belongs to
741
+ * @param {RouteEntry} mount
742
+ * @param {string} pathPrefix what the mounts above this one consumed
743
+ * @param {any[]} chain the layers that always run before the mount, which need no guard
744
+ * @param {any[]} inherited the guards from further out, since a mount two levels down is under
745
+ * everything written before either of them
746
+ * @returns {any[]|null} null when a path cannot be read segment by segment, which leaves the mount
747
+ * to ordinary dispatch rather than guessing about it
748
+ */
749
+ function guardsInside(router, mount, pathPrefix, chain, inherited) {
750
+ let guards = inherited;
751
+ for (const r of router._routes) {
752
+ if (r.routeKey > mount.routeKey) {
753
+ break;
754
+ }
755
+ if (r === mount || chain.includes(r)) {
756
+ continue;
757
+ }
758
+ if (typeof r.path !== "string") {
759
+ return null;
760
+ }
761
+ if (guards === inherited) {
762
+ guards = [...inherited];
763
+ }
764
+ guards.push({ path: pathPrefix + r.path, use: r.use === true, method: r.method, all: r.all === true });
765
+ }
766
+ return guards;
767
+ }
768
+
769
+ /**
770
+ * Notes which application is current before a mounted one is entered, so that exact one comes back.
771
+ *
772
+ * Only an application takes it back. Express restores req.app by putting the request prototype
773
+ * back, and it wraps a mounted application to do that only in Application#use: hang one off a plain
774
+ * Router and nothing restores it. Restoring regardless made a later res.send answer with the outer
775
+ * application's etag setting where express answers with the inner.
776
+ *
777
+ * And what comes back is what was current, not the entered application's parent. Those differ the
778
+ * moment a sub-app is entered from inside another sub-app a plain Router mounted, and reaching for
779
+ * `.parent` skipped a level: a 404 from the top application then carried an ETag under
780
+ * `app.set("etag", false)`. Found by fuzzing three levels of routers.
781
+ *
782
+ * The route is remembered alongside, so the pop can only take back what this same route put there.
783
+ *
784
+ * @param {Walk} walk
785
+ * @param {RouteEntry} route
786
+ * @param {Request} req
787
+ */
788
+ function rememberApp(walk, route, req) {
789
+ if (walk.router._isApplication && route.callbacks[0]?._isApplication) {
790
+ (req._appStack ??= []).push(route, req.app);
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Puts back what rememberApp noted, if this is the route that noted it.
796
+ *
797
+ * @param {RouteEntry} route
798
+ * @param {Request} req
799
+ */
800
+ function restoreApp(route, req) {
801
+ const stack = req._appStack;
802
+ if (stack !== undefined && stack.length > 0 && stack[stack.length - 2] === route) {
803
+ const app = stack.pop();
804
+ stack.pop();
805
+ useApp(req, app);
806
+ }
807
+ }
808
+
809
+ /**
810
+ * useApp
811
+ * @param {Request} req
812
+ * @param {any} app the application taking the request over
813
+ */
814
+ function useApp(req, app) {
815
+ req.app = app;
816
+ if (req.res) {
817
+ req.res.app = app;
818
+ }
819
+ // an app's own request/response extensions apply while it runs: express re-parents both
820
+ // objects on entering a mounted app, and this is the equivalent hop
821
+ if (app.request && Object.getPrototypeOf(req) !== app.request) {
822
+ Object.setPrototypeOf(req, app.request);
823
+ }
824
+ if (app.response && req.res && Object.getPrototypeOf(req.res) !== app.response) {
825
+ Object.setPrototypeOf(req.res, app.response);
826
+ }
827
+ }
828
+
829
+ // Every verb node knows about, which is the list the methods package hands Express, and "all" on
830
+ // top of it. Taken from node rather than written out: the written out one was missing acl, bind,
831
+ // link, rebind, source, unbind, unlink and unlock, and had four of the others twice.
832
+ //
833
+ // GET is left out on purpose. get() is declared in the class, because it doubles as the settings
834
+ // reader, and the loop at the end of this file would replace it.
835
+ const methods = ["all", ...METHODS.filter((method) => method !== "GET").map((method) => method.toLowerCase())];
836
+ const supportedUwsMethods = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"]);
837
+
838
+ // the same name rule patternToRegex reads, so a unicode name is found here too
839
+ const regExParam = /:([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/gu;
840
+
841
+ // Internals here are _underscore and not #private: a callable router is a function with the
842
+ // router's properties copied onto it, and a # field cannot be copied, so #routes would throw
843
+ // "Cannot read private member" on the first call.
844
+
845
+ // one intermediate prototype per class, built the first time a callable of that class is made
846
+ const callablePrototypes = new WeakMap();
847
+
848
+ /**
849
+ * The prototype for a callable router or app: the class prototype, with apply and call put back.
850
+ *
851
+ * Setting a function's prototype to a class prototype drops Function.prototype from the chain, and
852
+ * node calls a request listener with handler.apply. An intermediate object, so express.application
853
+ * stays in the chain. constructor and bind are not restored: the code asks constructor.name, and
854
+ * BIND is an HTTP verb, so app.bind registers a route as it does in Express.
855
+ *
856
+ * @param {object} classPrototype
857
+ * @returns {object}
858
+ */
859
+ function callablePrototypeFor(classPrototype) {
860
+ let prototype = callablePrototypes.get(classPrototype);
861
+ if (prototype) {
862
+ return prototype;
863
+ }
864
+ prototype = Object.create(classPrototype);
865
+ for (const name of ["apply", "call", "toString"]) {
866
+ Object.defineProperty(prototype, name, {
867
+ value: /** @type {any} */ (Function.prototype)[name],
868
+ writable: true,
869
+ configurable: true,
870
+ enumerable: false
871
+ });
872
+ }
873
+ callablePrototypes.set(classPrototype, prototype);
874
+ return prototype;
875
+ }
876
+
877
+ /**
878
+ * The default error page, the one Express produces: the stack in a pre, and nothing else. What
879
+ * reaches it has already been redacted when the environment calls for it.
880
+ *
881
+ * The text is escaped, which is not decoration. An error message can carry anything a client sent,
882
+ * and writing it into the page unescaped put that into the markup. The Content-Security-Policy on
883
+ * this response stops a script from running, but a policy is a second line and not the first.
884
+ *
885
+ * @param {any} err whatever was thrown, which need not be an Error
886
+ * @returns {string}
887
+ */
888
+ function generateErrorPageHtml(err) {
889
+ const text = String(err?.stack ?? err)
890
+ .replace(/&/g, "&amp;")
891
+ .replace(/</g, "&lt;")
892
+ .replace(/>/g, "&gt;")
893
+ .replace(/"/g, "&quot;")
894
+ .replace(/'/g, "&#39;")
895
+ .replace(/\n/g, "<br>")
896
+ .replace(/ {2}/g, " &nbsp;");
897
+ return (
898
+ `<!DOCTYPE html>\n` +
899
+ `<html lang="en">\n` +
900
+ `<head>\n` +
901
+ `<meta charset="utf-8">\n` +
902
+ `<title>Error</title>\n` +
903
+ `</head>\n` +
904
+ `<body>\n` +
905
+ `<pre>${text}</pre>\n` +
906
+ `</body>\n` +
907
+ `</html>\n`
908
+ );
909
+ }
910
+
911
+ module.exports = {
912
+ HAS_LETTER,
913
+ anyGuardHits,
914
+ resCodes,
915
+ resDecMethods,
916
+ layerFor,
917
+ routeLayer,
918
+ nativeDone,
919
+ nativeFail,
920
+ mountPrefixLength,
921
+ setMountedPath,
922
+ ownParamNames,
923
+ mergesParams,
924
+ EMPTY_INDICES,
925
+ buildLiteralIndex,
926
+ firstAtLeast,
927
+ mergeParams,
928
+ adoptPlainRequest,
929
+ logError,
930
+ onNativeAborted,
931
+ nativePreset,
932
+ hasErrorMiddleware,
933
+ checkHandlers,
934
+ CALLBACK_PLAIN,
935
+ CALLBACK_ERROR,
936
+ CALLBACK_ROUTER,
937
+ raiseDecodeFailure,
938
+ stepsOver,
939
+ couldAnswer,
940
+ shadowsLeaf,
941
+ guardsInside,
942
+ rememberApp,
943
+ restoreApp,
944
+ useApp,
945
+ methods,
946
+ supportedUwsMethods,
947
+ regExParam,
948
+ callablePrototypeFor,
949
+ generateErrorPageHtml
950
+ };