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.
package/src/request.js CHANGED
@@ -22,412 +22,30 @@ const accepts = require("accepts");
22
22
  const typeis = require("type-is");
23
23
  const parseRange = require("range-parser");
24
24
  const proxyaddr = require("proxy-addr");
25
- const { isIP } = require("node:net");
26
25
  const fresh = require("fresh");
27
26
  const parseQuery = require("./parse-query.js");
28
- const { Readable } = require("stream");
29
-
30
- // accepts, type-is, proxy-addr and fresh all declare a node IncomingMessage and read nothing off
31
- // it but .headers. This request is deliberately not one, so it is handed over as itself and the
32
- // declared shape is stepped around at each call.
33
- const asMessage = (req) => /** @type {any} */ (req);
34
-
35
- /**
36
- * Writes an address the way node writes socket.remoteAddress, which is inet_ntop's output and so
37
- * RFC 5952: leading zeros dropped from each group, the longest run of two or more zero groups
38
- * written as "::", and the last four bytes written in dotted form for the addresses that carry an
39
- * IPv4 one. uWS hands over the sixteen bytes, and writing them out in full gave req.ip
40
- * "0000:0000:0000:0000:0000:0000:0000:0001" where Express says "::1".
41
- *
42
- * @param {number[]} groups the eight 16-bit groups, most significant first
43
- * @returns {string}
44
- */
45
- function formatIPv6(groups) {
46
- // longest run of zero groups, leftmost on a tie, which is the run inet_ntop replaces
47
- let bestStart = -1;
48
- let bestLength = 0;
49
- for (let i = 0; i < 8; i++) {
50
- if (groups[i] !== 0) continue;
51
- let run = 1;
52
- while (i + run < 8 && groups[i + run] === 0) run++;
53
- if (run > bestLength) {
54
- bestStart = i;
55
- bestLength = run;
56
- }
57
- i += run - 1;
58
- }
59
- // a single zero group is written as "0", not as "::"
60
- if (bestLength < 2) {
61
- bestStart = -1;
62
- bestLength = 0;
63
- }
64
-
65
- // ::ffff:a.b.c.d, and the deprecated ::a.b.c.d. The test is inet_ntop's own, including that a
66
- // run of seven leading zeros never reaches it, since group 6 is inside the run by then.
67
- const mixed =
68
- bestStart === 0 &&
69
- (bestLength === 6 || (bestLength === 7 && groups[7] !== 1) || (bestLength === 5 && groups[5] === 0xffff));
70
-
71
- let out = "";
72
- for (let i = 0; i < 8; i++) {
73
- if (bestStart !== -1 && i >= bestStart && i < bestStart + bestLength) {
74
- if (i === bestStart) out += ":";
75
- continue;
76
- }
77
- if (i !== 0) out += ":";
78
- if (mixed && i === 6) {
79
- out += `${groups[6] >> 8}.${groups[6] & 0xff}.${groups[7] >> 8}.${groups[7] & 0xff}`;
80
- break;
81
- }
82
- out += groups[i].toString(16);
83
- }
84
- // a run reaching the end leaves a trailing group to close the "::"
85
- if (bestStart !== -1 && bestStart + bestLength === 8) out += ":";
86
- return out;
87
- }
88
-
89
- /**
90
- * Whether these sixteen bytes are an IPv4-mapped address, ::ffff:0:0/96: ten zero bytes and then
91
- * 0xffff. Ten comparisons rather than a loop, because this runs on every address that is read and
92
- * the first mismatch answers immediately for a real IPv6 peer.
93
- *
94
- * @param {Uint8Array} bytes exactly sixteen of them
95
- * @returns {boolean}
96
- */
97
- function isMappedIPv4(bytes) {
98
- return (
99
- bytes[10] === 0xff &&
100
- bytes[11] === 0xff &&
101
- bytes[0] === 0 &&
102
- bytes[1] === 0 &&
103
- bytes[2] === 0 &&
104
- bytes[3] === 0 &&
105
- bytes[4] === 0 &&
106
- bytes[5] === 0 &&
107
- bytes[6] === 0 &&
108
- bytes[7] === 0 &&
109
- bytes[8] === 0 &&
110
- bytes[9] === 0
111
- );
112
- }
113
-
114
- /**
115
- * Whether node would report an IPv4 peer of this app in mapped form, "::ffff:a.b.c.d". Node maps
116
- * it whenever the listener is dual stack, which is every listen() not given an IPv4 address to
117
- * bind. uWS already hands mapped peers over as sixteen bytes; four bytes only reach req.ip from a
118
- * v4-bound native listener or through the node shim, whose server supertest binds dual stack.
119
- *
120
- * @param {any} app the application the request arrived at
121
- * @returns {boolean}
122
- */
123
- function mapsIPv4Peer(app) {
124
- const host = app._listenHost;
125
- return !(host && isIP(host) === 4);
126
- }
127
-
128
- /** What µWS returns for a proxied address when no PROXY protocol preamble arrived. */
129
- const emptyAddress = new ArrayBuffer(0);
130
-
131
- const discardedDuplicates = new Set([
132
- "age",
133
- "authorization",
134
- "content-length",
135
- "content-type",
136
- "etag",
137
- "expires",
138
- "from",
139
- "host",
140
- "if-modified-since",
141
- "if-unmodified-since",
142
- "last-modified",
143
- "location",
144
- "max-forwards",
145
- "proxy-authorization",
146
- "referer",
147
- "retry-after",
148
- "server",
149
- "user-agent"
150
- ]);
151
-
152
- // 128 KB of body buffered before uWS is asked to pause
153
- const READABLE_OPTIONS = { highWaterMark: 128 * 1024 };
154
-
155
- // The methods node's parser accepts, which is the set a request can arrive with behind Express and
156
- // the set a route can be registered for here. µWS accepts any token, so without this a line like
157
- // `{"a":1}GET /path HTTP/1.1` is a request to it, with `{"A":1}GET` as the method. See _mustRefuse.
158
- const KNOWN_METHODS = new Set(require("http").METHODS);
159
-
160
- /**
161
- * Whether a request target is bytes node's parser would have accepted, which is printable ASCII
162
- * and nothing else.
163
- *
164
- * µWS takes the target as it finds it and decodes it as UTF-8, so `GET /café` arrives here
165
- * as a path with an é in it and the overlong encoding of a slash arrives as replacement
166
- * characters. Node refuses both with a 400 before any application sees them, and it has to: what
167
- * reaches req.url otherwise is not what is on the wire, and a proxy in front reading the same
168
- * bytes can disagree with this server about which path was asked for. Control characters are µWS's
169
- * own to refuse and it does, so the test is one comparison per character rather than two.
170
- *
171
- * @param {string} target the path or the query string, as µWS decoded it
172
- * @returns {boolean}
173
- */
174
- function isAsciiTarget(target) {
175
- for (let i = 0; i < target.length; i++) {
176
- if (target.charCodeAt(i) > 0x7e) {
177
- return false;
178
- }
179
- }
180
- return true;
181
- }
182
-
183
- /**
184
- * Whether a transfer-encoding leaves the body's length knowable, which is RFC 9112's rule that
185
- * `chunked` comes last. `gzip, chunked` is fine and `chunked, gzip` is not: with a coding applied
186
- * after the framing one, nothing can say where the body ends, and node answers 400 rather than
187
- * guess. µWS guesses, and what it guesses wrong becomes the next request on the connection.
188
- *
189
- * Read per header rather than over the joined value, so a request splitting the list across two
190
- * transfer-encoding headers is refused even when the codings would be legal joined up. That is
191
- * stricter than node by a hair, on a shape nothing sends, and stricter is the safe direction here.
192
- *
193
- * @param {string} value one transfer-encoding header, as uWS hands it over
194
- * @returns {boolean}
195
- */
196
- function endsWithChunked(value) {
197
- const last = value.slice(value.lastIndexOf(",") + 1).trim();
198
- // a coding may carry parameters, which are not part of its name
199
- const semicolon = last.indexOf(";");
200
- if ((semicolon === -1 ? last : last.slice(0, semicolon)).trim().toLowerCase() !== "chunked") {
201
- return false;
202
- }
203
- // and only once. "chunked, chunked" ends with it and is still nonsense: a sender may not frame
204
- // a body twice, and where node refuses the request outright µWS frames it as one chunked body
205
- // and reads whatever follows as the next request on the connection
206
- const codings = value.split(",");
207
- let chunkedCount = 0;
208
- for (const coding of codings) {
209
- const parameter = coding.indexOf(";");
210
- if ((parameter === -1 ? coding : coding.slice(0, parameter)).trim().toLowerCase() === "chunked") {
211
- chunkedCount++;
212
- }
213
- }
214
- return chunkedCount === 1;
215
- }
216
-
217
- /**
218
- * Whether a Connection header says the connection ends with this response.
219
- *
220
- * It is a list, and "keep-alive, close" closes as much as "close" alone does. Compared against an
221
- * exact "close", this server kept a connection the client had said it was done with, and then read
222
- * the bytes after it as another request: node closes there, so the two disagreed on how many
223
- * requests the same bytes carried, which is what a desync is.
224
- *
225
- * Written as a scan rather than a split and a lowercase, because almost every request that carries
226
- * this header carries "keep-alive", and both of those allocate per request.
227
- *
228
- * @param {string} value as µWS hands it over
229
- * @returns {boolean}
230
- */
231
- function saysClose(value) {
232
- // what clients actually send, almost always: two interned compares answer before the scan
233
- if (value === "keep-alive") {
234
- return false;
235
- }
236
- if (value === "close") {
237
- return true;
238
- }
239
- const length = value.length;
240
- let at = 0;
241
- while (at < length) {
242
- while (at < length && (value.charCodeAt(at) === 0x20 || value.charCodeAt(at) === 0x09)) {
243
- at++;
244
- }
245
- const start = at;
246
- while (at < length && value.charCodeAt(at) !== 0x2c) {
247
- at++;
248
- }
249
- let end = at;
250
- while (end > start && (value.charCodeAt(end - 1) === 0x20 || value.charCodeAt(end - 1) === 0x09)) {
251
- end--;
252
- }
253
- if (
254
- end - start === 5 &&
255
- (value.charCodeAt(start) | 0x20) === 0x63 &&
256
- (value.charCodeAt(start + 1) | 0x20) === 0x6c &&
257
- (value.charCodeAt(start + 2) | 0x20) === 0x6f &&
258
- (value.charCodeAt(start + 3) | 0x20) === 0x73 &&
259
- (value.charCodeAt(start + 4) | 0x20) === 0x65
260
- ) {
261
- return true;
262
- }
263
- at++;
264
- }
265
- return false;
266
- }
267
-
268
- /**
269
- * The path of the url a request carries right now, without the query.
270
- *
271
- * Express reads it off req.url on every access, so a middleware that assigns req.url is seen by
272
- * whatever runs next, the callback after it in the same route included: the router only takes a
273
- * rewrite over at its next hop. The cached field answers while the two agree, which is every read
274
- * of a request nobody rewrote.
275
- *
276
- * @param {any} req
277
- * @returns {string}
278
- */
279
- function currentPath(req) {
280
- const url = req.url;
281
- if (url === req._lastUrl) {
282
- return req._path;
283
- }
284
- const query = url.indexOf("?");
285
- return query === -1 ? url : url.slice(0, query);
286
- }
287
-
288
- /**
289
- * Whether a content-length is a plain count of bytes, which is the only thing RFC 9112 allows.
290
- *
291
- * uWS trims the spaces around the value and then takes whatever is left, so "", "abc", "+1", "-1",
292
- * "0x10" and "1e2" all arrive here. Every one of them makes uWS frame the request as carrying no
293
- * body, and what the client sent as a body is then read as the next request on the connection.
294
- * Node's parser refuses all of them outright, and so does this.
295
- *
296
- * @param {string} value as uWS hands it over
297
- * @returns {boolean}
298
- */
299
- function isByteCount(value) {
300
- if (value.length === 0) {
301
- return false;
302
- }
303
- for (let i = 0; i < value.length; i++) {
304
- const code = value.charCodeAt(i);
305
- if (code < 0x30 || code > 0x39) {
306
- return false;
307
- }
308
- }
309
- // A count nothing can represent is not a count. Node refuses one that overflows, and µWS framed
310
- // the request as if it had said something else, which put the bytes after it in a request of
311
- // their own. The length test first, so an ordinary value never parses.
312
- if (value.length > 15 && Number(value) > Number.MAX_SAFE_INTEGER) {
313
- return false;
314
- }
315
- return true;
316
- }
27
+ const { isIP } = require("node:net");
28
+ const { LazyReadable } = require("./lazy-readable.js");
29
+ const {
30
+ asMessage,
31
+ formatIPv6,
32
+ isMappedIPv4,
33
+ mapsIPv4Peer,
34
+ emptyAddress,
35
+ discardedDuplicates,
36
+ KNOWN_METHODS,
37
+ isAsciiTarget,
38
+ endsWithChunked,
39
+ saysClose,
40
+ currentPath,
41
+ isByteCount
42
+ } = require("./request-utils.js");
317
43
 
318
44
  // Whose headers the shared collector below is filling. uWS's forEach is synchronous and runs no
319
45
  // user code, so the handoff cannot interleave; module-level so the callback exists once instead
320
46
  // of once per request.
321
47
  let currentRequest = null;
322
48
 
323
- /**
324
- * A Readable that has not been built yet.
325
- *
326
- * Every request pays for the stream and almost none of them use it: a GET carries no body, and the
327
- * bodies that do arrive are collected by µWS and handed to the parsers without the stream being
328
- * touched. Measured on this machine, running Readable's constructor costs about 90ns of the 900ns
329
- * a hello-world request costs in total, which is a tenth of it for a facility nobody asked for.
330
- *
331
- * So the chain says Readable and the constructor does not run. `Request extends LazyReadable`, and
332
- * LazyReadable's prototype is Readable's, which keeps `req instanceof Readable` true and every
333
- * Readable method reachable; what is missing is `_readableState`, and that is built on the first
334
- * touch. A derived class cannot skip its super() call, but a base class with nothing in it costs
335
- * nothing to call.
336
- *
337
- * The wrapping below is generated rather than written out, and deliberately: every own member of
338
- * Readable's prototype gets a version that materialises first, so there is no list to keep in step
339
- * and no door left unguarded. Missing one would not be a slow path, it would be a TypeError on
340
- * `undefined._readableState` in whatever corner of a stream nobody tested.
341
- */
342
- class LazyReadableBase {}
343
- Object.setPrototypeOf(LazyReadableBase.prototype, Readable.prototype);
344
- Object.setPrototypeOf(LazyReadableBase, Readable);
345
-
346
- // what the chain says at runtime, said again for the type checker, which cannot see a prototype
347
- // being reassigned: everything a Readable offers is reachable from a Request, and is a Readable's
348
- const LazyReadable = /** @type {typeof Readable} */ (/** @type {unknown} */ (LazyReadableBase));
349
-
350
- /**
351
- * Builds the stream this object has been pretending to be. Idempotent: everything that can be
352
- * reached from outside goes through it, so it is called far more often than it does anything.
353
- *
354
- * EventEmitter's init keeps an _events that is already there, so listeners added before this
355
- * survive it.
356
- *
357
- * @param {any} stream
358
- */
359
- function materialise(stream) {
360
- if (stream._readableState === undefined) {
361
- Readable.call(stream, READABLE_OPTIONS);
362
- }
363
- }
364
-
365
- for (const member of [
366
- ...Object.getOwnPropertyNames(Readable.prototype),
367
- ...Object.getOwnPropertySymbols(Readable.prototype)
368
- ]) {
369
- // the constructor is not a door, and `readable` is handled below because a request writes it
370
- // and writing it must not build the very thing this is avoiding
371
- if (member === "constructor" || member === "readable") {
372
- continue;
373
- }
374
- const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Readable.prototype, member));
375
- if (typeof descriptor.value === "function") {
376
- const inner = descriptor.value;
377
- Object.defineProperty(LazyReadableBase.prototype, member, {
378
- ...descriptor,
379
- /** @this {any} @param {...any} args */
380
- value: function (...args) {
381
- materialise(this);
382
- return inner.apply(this, args);
383
- }
384
- });
385
- } else if (descriptor.get || descriptor.set) {
386
- const innerGet = descriptor.get;
387
- const innerSet = descriptor.set;
388
- Object.defineProperty(LazyReadableBase.prototype, member, {
389
- ...descriptor,
390
- get: innerGet
391
- ? /** @this {any} */ function () {
392
- materialise(this);
393
- return innerGet.call(this);
394
- }
395
- : undefined,
396
- set: innerSet
397
- ? /** @this {any} @param {any} value */ function (value) {
398
- materialise(this);
399
- innerSet.call(this, value);
400
- }
401
- : undefined
402
- });
403
- }
404
- }
405
-
406
- const nodeReadable = /** @type {PropertyDescriptor} */ (
407
- Object.getOwnPropertyDescriptor(Readable.prototype, "readable")
408
- );
409
-
410
- // `readable` on its own: a request sets it while it is being built, and node's setter is a no-op
411
- // without the state anyway, so the flag is kept as a plain field until there is a stream to ask
412
- Object.defineProperty(LazyReadableBase.prototype, "readable", {
413
- configurable: true,
414
- enumerable: false,
415
- /** @this {any} */
416
- get: function () {
417
- return this._readableState === undefined
418
- ? this._readableFlag === true
419
- : /** @type {any} */ (nodeReadable.get).call(this);
420
- },
421
- /** @this {any} @param {any} value */
422
- set: function (value) {
423
- if (this._readableState === undefined) {
424
- this._readableFlag = !!value;
425
- return;
426
- }
427
- /** @type {any} */ (nodeReadable.set).call(this, value);
428
- }
429
- });
430
-
431
49
  module.exports = class Request extends LazyReadable {
432
50
  /** @type {Record<string, any>|null} */
433
51
  #cachedHeaders = null;
@@ -438,10 +56,9 @@ module.exports = class Request extends LazyReadable {
438
56
  /**
439
57
  * Every header, flat: name then value, name then value.
440
58
  *
441
- * An array of pairs meant one array allocated per header on every request, and a request
442
- * carries eight or ten of them, so everything that reads this walks it two at a time. The
443
- * names are lowercase by contract: uWS lowers them on the wire and the node shim lowers
444
- * them in its forEach, so readers compare without lowering again.
59
+ * An array of pairs meant one array per header on every request, and a request carries eight or
60
+ * ten, so everything that reads this walks it two at a time. The names are lowercase by
61
+ * contract: uWS lowers them on the wire and the node shim lowers them in its forEach.
445
62
  *
446
63
  * @type {string[]}
447
64
  */
@@ -458,10 +75,9 @@ module.exports = class Request extends LazyReadable {
458
75
 
459
76
  // `body` is deliberately not declared here. A class field would put the property on every
460
77
  // request, and on Express there is none until a body parser assigns one. `"body" in req` is how
461
- // a library asks whether the body has already been read, and tRPC's express adapter asks
462
- // exactly that: answering yes on a request nobody had parsed handed it an undefined body and
463
- // turned every mutation into "Unexpected end of JSON input". Its type lives in types.d.ts,
464
- // where the rest of the public request surface is described.
78
+ // a library asks whether the body was read, and tRPC's express adapter does exactly that:
79
+ // answering yes turned every mutation into "Unexpected end of JSON input". Its type is in
80
+ // types.d.ts, with the rest of the public request surface.
465
81
 
466
82
  /**
467
83
  * The response this request arrived with, linked so either reaches the other.
@@ -619,22 +235,21 @@ module.exports = class Request extends LazyReadable {
619
235
  _sawContentLength;
620
236
 
621
237
  /**
622
- * Whether this request must not be routed at all. Node's parser refuses each of these outright
623
- * and answers 400; every one of them is a way for bytes the client did not send as a request to
624
- * be served as one, which is request smuggling.
238
+ * Whether this request must not be routed at all. Node's parser refuses each of these and
239
+ * answers 400; every one is a way for bytes the client did not send as a request to be served
240
+ * as one, which is request smuggling.
625
241
  *
626
242
  * a repeated content-length uWS frames on the first and drops the rest, so a proxy in
627
- * front reading the last one instead forwards bytes uWS then
628
- * answers as a second, pipelined request
243
+ * front reading the last one forwards bytes uWS then answers
244
+ * as a second, pipelined request
629
245
  * one that is not a byte count uWS keeps whatever is left after trimming, an empty value
630
- * included, and frames the request as carrying no body at all,
631
- * which turns the body the client sent into that same second
632
- * request. See isByteCount
633
- * a method nobody defines uWS takes any token as the method, so anything at all
634
- * followed by a space and a path is a request line to it. A
635
- * request with no content-length and no transfer-encoding has
636
- * no body, so the bytes after it are the next request: node
637
- * reads them and answers 400, uWS served them. See KNOWN_METHODS
246
+ * included, and frames the request as carrying no body, which
247
+ * turns the body the client sent into that second request.
248
+ * See isByteCount
249
+ * a method nobody defines uWS takes any token as the method, so anything followed by a
250
+ * space and a path is a request line to it. With no
251
+ * content-length and no transfer-encoding there is no body, so
252
+ * the bytes after it are the next request. See KNOWN_METHODS
638
253
  *
639
254
  * Declared for the same reason as rawIp.
640
255
  *
@@ -690,9 +305,9 @@ module.exports = class Request extends LazyReadable {
690
305
  * @param {any} req the uWS request, readable only during this call
691
306
  * @param {any} res the uWS response
692
307
  * @param {any} app the application or router this request arrived at
693
- * @param {any} [preset] a literal native registration's constants: µWS matched the URL byte
694
- * for byte against that exact pattern and dispatched by method, so path, method and what
695
- * derives from them are known without asking
308
+ * @param {any} [preset] a literal native registration's constants: uWS matched the URL byte for
309
+ * byte against that exact pattern and dispatched by method, so path, method and what derives
310
+ * from them are known without asking
696
311
  * @param {any} [skipHolder] where a granted header skip lives: the preset itself for a
697
312
  * literal registration, a holder of its own for a parameterised one
698
313
  */
@@ -705,33 +320,25 @@ module.exports = class Request extends LazyReadable {
705
320
  // The chain behind this registration provably never reads a header, so instead of
706
321
  // copying them all out of uWS the constructor asks for the ones that steer the
707
322
  // framework itself: body framing, keep-alive, and the conditional pair. A GET that
708
- // does declare a body is the rare case, and the parsers and the stream want the
709
- // whole picture, so it takes the full copy.
323
+ // declares a body takes the full copy.
710
324
  //
711
- // A handful of named reads against one forEach looks like it should lose, and does
712
- // not: measured at seven reads they were flat at 0.75us however many headers are on
713
- // the wire, since each one is a napi crossing and the scan behind it is nothing,
714
- // while the copy pays a hop back into JS per header and grows, 1.16us at four
715
- // headers, 1.61 at eight, 2.90 at sixteen. They do not cross, and the gap widens
716
- // exactly where real traffic lives, since a browser sends a dozen or more. The body
717
- // case pays two reads and then copies anyway, which is 0.2us on a request that is
718
- // about to read a body.
325
+ // A handful of named reads beats one forEach: measured at seven reads they are flat at
326
+ // 0.75us however many headers are on the wire, since each one is a napi crossing, while
327
+ // the copy pays a hop back into JS per header and grows, 1.16us at four headers, 1.61
328
+ // at eight, 2.90 at sixteen. The body case pays two reads and then copies anyway, 0.2us.
719
329
  //
720
- // accept is not read: nothing on a granted chain consumes it, the error and 404
721
- // pages are fixed HTML that never negotiate.
330
+ // accept is not read: nothing on a granted chain consumes it, the error and 404 pages
331
+ // are fixed HTML that never negotiate.
722
332
  const length = req.getHeader("content-length");
723
333
  const transferEncoding = req.getHeader("transfer-encoding");
724
334
  // A content-length of "0" declares no body and used to stay on the cheap side, but
725
- // getHeader only ever returns the first of a repeated header, so a duplicate cannot be
726
- // seen from here, and a duplicate has to be refused rather than routed: see
727
- // _mustRefuse. Anything that says a word about framing takes the full copy instead.
335
+ // getHeader only returns the first of a repeated header, so a duplicate cannot be seen
336
+ // from here and has to be refused rather than routed, see _mustRefuse. Anything that
337
+ // says a word about framing takes the full copy instead.
728
338
  //
729
339
  // One shape stays invisible here, a content-length present with an empty value: uWS
730
- // answers "" for that and for a header that was never sent, and nothing in its API
731
- // tells them apart. It frames both as carrying no body, which is the right reading of
732
- // the second, so this server stays consistent with itself either way. The full copy
733
- // below does refuse it, which is every request except a GET whose whole chain provably
734
- // reads no header at all.
340
+ // answers "" for that and for a header never sent, and nothing in its API tells them
341
+ // apart. It frames both as carrying no body. The full copy below does refuse it.
735
342
  if (length !== "" || transferEncoding !== "") {
736
343
  currentRequest = this;
737
344
  this._req.forEach(Request.#collectHeader);
@@ -772,11 +379,9 @@ module.exports = class Request extends LazyReadable {
772
379
  }
773
380
  this.routeCount = 1;
774
381
  this.app = app;
775
- // both forms are kept, because both are asked for: the query with its "?" goes into
776
- // req.url, and req.query parses the raw one. Keeping only the first meant slicing the "?"
777
- // back off for every request that reads req.query. When the chain provably reads
778
- // neither, the native call is not made at all: the framework's own answers, the 404
779
- // included, are written from the path alone
382
+ // both forms are kept because both are asked for: the query with its "?" goes into req.url,
383
+ // and req.query parses the raw one. When the chain provably reads neither, the native call
384
+ // is not made at all: the framework's own answers are written from the path alone
780
385
  if (skipHolder !== undefined && skipHolder.skipQuery) {
781
386
  this._rawQuery = "";
782
387
  this.urlQuery = "";
@@ -831,12 +436,10 @@ module.exports = class Request extends LazyReadable {
831
436
  this._isHead = skipHolder.isHead;
832
437
  } else {
833
438
  this.method = rawMethod.toUpperCase();
834
- // node's parser knows a fixed set and refuses everything else; µWS takes the token
439
+ // node's parser knows a fixed set and refuses everything else, uWS takes the token
835
440
  // as it finds it, so a request line is anything with a space in it. Compared before
836
- // the uppercasing on purpose: a method is case sensitive, node refuses "post", and
837
- // µWS folds it to POST and serves it. Only asked of a method the framework cannot
838
- // route anyway, since a route can only be registered for one of these, see the loop
839
- // that builds the verb methods at the end of router.js
441
+ // the uppercasing on purpose: a method is case sensitive, node refuses "post" and
442
+ // uWS folds it to POST and serves it
840
443
  if (!KNOWN_METHODS.has(rawMethod)) {
841
444
  this._mustRefuse = true;
842
445
  }
@@ -857,12 +460,10 @@ module.exports = class Request extends LazyReadable {
857
460
  // Two Sets per request, for two things almost no request needs.
858
461
  //
859
462
  // _matchedMethods collects the verbs a path answers so an OPTIONS request can be told what
860
- // they are, and every place that reads it asks _isOptions first, so it is built only for
861
- // the requests that are one.
463
+ // they are, and every reader asks _isOptions first, so it is built only for those.
862
464
  //
863
- // _paramCalled remembers, per router, what each app.param() callback was called with and
864
- // what it left behind, so it is only wanted by an application that uses app.param at all.
865
- // The router builds it the first time it has something to put in it.
465
+ // _paramCalled remembers, per router, what each app.param() callback was called with, so
466
+ // only an application using app.param wants it. The router builds it when it has something.
866
467
  this._matchedMethods = this._isOptions ? new Set() : null;
867
468
  this._paramCalled = null;
868
469
  // null for the same reason as the two above: a request that never enters a mount never
@@ -892,10 +493,8 @@ module.exports = class Request extends LazyReadable {
892
493
  }
893
494
 
894
495
  // A body exists on the wire only when the request declares one, content-length or
895
- // transfer-encoding, whatever the verb, and that evidence was spotted during the header
896
- // copy. The verb list and the "body methods" settings read this branch used to pay per
897
- // request said nothing the headers had not already said; the setting still gates the
898
- // body parsers, which is where it matters
496
+ // transfer-encoding, whatever the verb, and that was spotted during the header copy. The
497
+ // verb list this used to read said nothing the headers had not already said
899
498
  if (/** @type {any} */ (this)._declaresBody) {
900
499
  this._subscribeBody();
901
500
  } else {
@@ -1004,8 +603,7 @@ module.exports = class Request extends LazyReadable {
1004
603
  * a visitor who has gone away can be stopped. `@angular/ssr` reads it when it builds a web
1005
604
  * Request out of this one, which is how an SSR render learns to give up.
1006
605
  *
1007
- * Made on the first ask rather than for every request: most requests never look at it, and an
1008
- * AbortController each would be an allocation nobody reads.
606
+ * Made on the first ask: most requests never look at it.
1009
607
  *
1010
608
  * @returns {AbortSignal}
1011
609
  */
@@ -1095,11 +693,9 @@ module.exports = class Request extends LazyReadable {
1095
693
  if (this._mountSlash !== true) {
1096
694
  return this._originalPath.slice(0, this._consumed);
1097
695
  }
1098
- // Express drops one trailing slash off each mount before joining them, so this is a join
1099
- // of the pieces rather than one slice of the path: a RegExp mount ending in "/" matched
1100
- // against "/a//b" takes "/a/" and reads back as "/a", and what the mount below it took is
1101
- // appended to that rather than to the original. Only a RegExp mount can take a trailing
1102
- // slash, a registered path having had it removed, so almost every request answers above.
696
+ // Express drops one trailing slash off each mount before joining them, so this is a join of
697
+ // the pieces and not one slice of the path: a RegExp mount ending in "/" matched against
698
+ // "/a//b" takes "/a/" and reads back as "/a". Only a RegExp mount can take a trailing slash
1103
699
  let out = "";
1104
700
  let at = 0;
1105
701
  for (let taken of this._stack) {
@@ -1324,37 +920,33 @@ module.exports = class Request extends LazyReadable {
1324
920
  * object, so a key like "__proto__" cannot reach Object.prototype. No setter, so assigning to
1325
921
  * req.query throws as it does on Express.
1326
922
  *
1327
- * Every read answers a new object, because express's getter re-parses on every read and so hands
1328
- * one back too. Two consequences an application can see, and both of them bite: `req.query` is
1329
- * never the object another reader holds, and a write to a key of it is gone by the next read.
1330
- * That second one is how express-validator's sanitisers behave: `.trim()` on a query parameter
1331
- * changes nothing an ordinary handler will see, which is why it also offers matchedData(). With
1332
- * the parse cached and handed out as itself, the sanitised value leaked into req.query here and
1333
- * a handler written against express read a trimmed value where express gives it the raw one.
923
+ * Every read answers a new object, because express re-parses on every read and hands one back
924
+ * too. So req.query is never the object another reader holds, and a write to a key of it is
925
+ * gone by the next read. That is how express-validator's sanitisers behave: `.trim()` on a
926
+ * query parameter changes nothing an ordinary handler sees. With the parse cached and handed
927
+ * out as itself, the sanitised value leaked into req.query here.
1334
928
  *
1335
- * And that is why there is no cache of the object: the fresh object comes from the raw
1336
- * string, not from copying a kept parse. As first shipped this was parse-once-copy-per-read,
1337
- * and the copy was the expensive half: Object.assign between null-prototype objects, which
1338
- * live in V8's dictionary mode, measured 638ns for a two-parameter query where parsing the
1339
- * same string measures 119ns, and on a benchmark whose every request carries such a query it
1340
- * cost +1.5us of CPU per request, which a public arena saw as -8% on its query-carrying rows.
929
+ * So there is no cache of the object: the fresh object comes from the raw string, not from
930
+ * copying a kept parse. Parse-once-copy-per-read was the first shape shipped, and the copy was
931
+ * the expensive half: Object.assign between null-prototype objects, which live in V8's
932
+ * dictionary mode, measured 638ns for a two-parameter query where parsing the same string
933
+ * measures 119ns, so +1.5us of CPU per request, which a public arena saw as -8% on its
934
+ * query-carrying rows.
1341
935
  *
1342
- * The default parser does keep the decoded pairs of its first parse, and a later read of the
1343
- * same raw string replays the stores into a fresh null-prototype object: identical output,
1344
- * still nothing shared between reads. A repeated key cannot be replayed and re-parses.
936
+ * The default parser keeps the decoded pairs of its first parse and replays the stores into a
937
+ * fresh null-prototype object: same output, nothing shared between reads. A repeated key
938
+ * cannot be replayed and re-parses.
1345
939
  *
1346
940
  * @returns {Record<string, any>}
1347
941
  */
1348
942
  get query() {
1349
943
  const qp = this.app._hot().queryParserFn;
1350
- // the vendored default already answers on a bare null prototype, so it goes out as is;
1351
- // any other parser is copied onto one, which is what kept fast-querystring's result from
1352
- // inspecting as "Empty <[Object: null prototype] {}>" where Express shows the bare form
1353
- // A parser of the application's own is handed what express hands it, which is
1354
- // parseurl's `query`: null when the url carries no "?" at all, and the text after it
1355
- // otherwise, the empty string included. Passing "" for both meant a parser written for
1356
- // express, which may check for null before it reads the string, saw a request that had no
1357
- // query as one with an empty query. The two built in parsers take the raw string.
944
+ // the vendored default already answers on a bare null prototype, so it goes out as is; any
945
+ // other parser is copied onto one, which kept fast-querystring's result from inspecting as
946
+ // "Empty <[Object: null prototype] {}>" where Express shows the bare form.
947
+ // A parser of the application's own is handed what express hands it, parseurl's `query`:
948
+ // null when the url carries no "?", the text after it otherwise, empty string included.
949
+ // Passing "" for both made a parser written for express see no query as an empty query.
1358
950
  if (!qp) {
1359
951
  return Object.create(null);
1360
952
  }
@@ -1436,12 +1028,11 @@ module.exports = class Request extends LazyReadable {
1436
1028
  * The peer address bytes, from the socket or, when the application asked for it, from a PROXY
1437
1029
  * protocol preamble the load balancer in front of this server sent ahead of the request.
1438
1030
  *
1439
- * The setting is off by default and has to stay that way. µWS parses the preamble from whoever
1440
- * sends it, with nothing to ask for it at listen time and no way to restrict who may, so an
1441
- * application that took the address unconditionally would let any client claim any address:
1442
- * the first sixteen bytes of a connection are enough to become 10.0.0.1 for a rate limiter, an
1443
- * allow list or an audit log. Turn it on only when nothing can reach this server except the
1444
- * proxy in front of it.
1031
+ * The setting is off by default and has to stay that way. uWS parses the preamble from whoever
1032
+ * sends it, with no way to restrict who may, so an application that took the address
1033
+ * unconditionally would let any client claim any address: the first sixteen bytes of a
1034
+ * connection are enough to become 10.0.0.1 for a rate limiter or an allow list. Turn it on only
1035
+ * when nothing can reach this server except the proxy in front of it.
1445
1036
  *
1446
1037
  * @returns {ArrayBuffer} the socket's own address when no preamble arrived
1447
1038
  */
@@ -1498,11 +1089,9 @@ module.exports = class Request extends LazyReadable {
1498
1089
  } else if (rawIp.byteLength === 16) {
1499
1090
  const bytes = new Uint8Array(rawIp);
1500
1091
  if (isMappedIPv4(bytes)) {
1501
- // ::ffff:a.b.c.d, which is what a dual stack listener hands over for every IPv4
1502
- // peer, so it is what nearly every request here is. The general path below reaches
1503
- // the same string through a DataView, an array of eight groups and a scan for the
1504
- // longest run of zeros, and measured 157ns more per request for it. Anything that
1505
- // reads req.ip pays that once, and morgan reads it on every line it writes.
1092
+ // ::ffff:a.b.c.d, what a dual stack listener hands over for every IPv4 peer, so
1093
+ // nearly every request here. The general path below reaches the same string through
1094
+ // a DataView and a scan for the longest zero run, 157ns more per request
1506
1095
  ip = "::ffff:" + bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
1507
1096
  } else {
1508
1097
  // ipv6
@@ -1543,13 +1132,12 @@ module.exports = class Request extends LazyReadable {
1543
1132
  }
1544
1133
 
1545
1134
  /**
1546
- * Cuts this request loose from the µWS response it arrived on, keeping the two things only
1135
+ * Cuts this request loose from the uWS response it arrived on, keeping the two things only
1547
1136
  * that response could answer.
1548
1137
  *
1549
- * A websocket upgrade hands the request to the socket, which outlives the response by the
1550
- * whole life of the connection. Reading the peer address through the freed response is not
1551
- * an error but a use after free, so the values are taken while it is still alive and an
1552
- * inert stand-in answers anything that asks later.
1138
+ * A websocket upgrade hands the request to the socket, which outlives the response. Reading the
1139
+ * peer address through the freed response is a use after free, so the values are taken while it
1140
+ * is still alive and an inert stand-in answers later.
1553
1141
  */
1554
1142
  _detachFromResponse() {
1555
1143
  const uwsRes = this._res;
@@ -1756,12 +1344,12 @@ module.exports = class Request extends LazyReadable {
1756
1344
  }
1757
1345
 
1758
1346
  /**
1759
- * The request headers as node presents them: lowercased names, and repeats folded the way
1760
- * node folds them. Set-Cookie stays an array, Cookie is joined with "; ", the fields listed in
1761
- * discardedDuplicates keep only the first value, and everything else is joined with ", ".
1347
+ * The request headers as node presents them: lowercased names, and repeats folded the way node
1348
+ * folds them. Set-Cookie stays an array, Cookie is joined with "; ", the fields listed in
1349
+ * discardedDuplicates keep only the first value, everything else is joined with ", ".
1762
1350
  *
1763
- * Built on first read and cached, since the raw entries are what routing works from and most
1764
- * requests never ask for this at all.
1351
+ * Built on first read and cached: routing works from the raw entries and most requests never
1352
+ * ask for this.
1765
1353
  *
1766
1354
  * @returns {Record<string, any>}
1767
1355
  */