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