undici 5.18.0 → 5.19.1

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.
@@ -63,7 +63,8 @@ Returns: `MockInterceptor` corresponding to the input options.
63
63
 
64
64
  We can define the behaviour of an intercepted request with the following options.
65
65
 
66
- * **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define this as a callback to read incoming request data. Default for `responseOptions` is `{}`.
66
+ * **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define the replyData as a callback to read incoming request data. Default for `responseOptions` is `{}`.
67
+ * **reply** `(callback: MockInterceptor.MockReplyOptionsCallback) => MockScope` - define a reply for a matching request, allowing dynamic mocking of all reply options rather than just the data.
67
68
  * **replyWithError** `(error: Error) => MockScope` - define an error for a matching request to throw.
68
69
  * **defaultReplyHeaders** `(headers: Record<string, string>) => MockInterceptor` - define default headers to be included in subsequent replies. These are in addition to headers on a specific reply.
69
70
  * **defaultReplyTrailers** `(trailers: Record<string, string>) => MockInterceptor` - define default trailers to be included in subsequent replies. These are in addition to trailers on a specific reply.
@@ -83,7 +83,8 @@ function getSetCookies (headers) {
83
83
  return []
84
84
  }
85
85
 
86
- return cookies.map((pair) => parseSetCookie(pair[1]))
86
+ // In older versions of undici, cookies is a list of name:value.
87
+ return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
87
88
  }
88
89
 
89
90
  /**
@@ -304,6 +304,9 @@ function processHeader (request, key, val) {
304
304
  key.length === 4 &&
305
305
  key.toLowerCase() === 'host'
306
306
  ) {
307
+ if (headerCharRegex.exec(val) !== null) {
308
+ throw new InvalidArgumentError(`invalid ${key} header`)
309
+ }
307
310
  // Consumed by Client
308
311
  request.host = val
309
312
  } else if (
package/lib/core/util.js CHANGED
@@ -213,25 +213,42 @@ function parseHeaders (headers, obj = {}) {
213
213
  for (let i = 0; i < headers.length; i += 2) {
214
214
  const key = headers[i].toString().toLowerCase()
215
215
  let val = obj[key]
216
+
217
+ const encoding = key.length === 19 && key === 'content-disposition'
218
+ ? 'latin1'
219
+ : 'utf8'
220
+
216
221
  if (!val) {
217
222
  if (Array.isArray(headers[i + 1])) {
218
223
  obj[key] = headers[i + 1]
219
224
  } else {
220
- obj[key] = headers[i + 1].toString()
225
+ obj[key] = headers[i + 1].toString(encoding)
221
226
  }
222
227
  } else {
223
228
  if (!Array.isArray(val)) {
224
229
  val = [val]
225
230
  obj[key] = val
226
231
  }
227
- val.push(headers[i + 1].toString())
232
+ val.push(headers[i + 1].toString(encoding))
228
233
  }
229
234
  }
230
235
  return obj
231
236
  }
232
237
 
233
238
  function parseRawHeaders (headers) {
234
- return headers.map(header => header.toString())
239
+ const ret = []
240
+ for (let n = 0; n < headers.length; n += 2) {
241
+ const key = headers[n + 0].toString()
242
+
243
+ const encoding = key.length === 19 && key.toLowerCase() === 'content-disposition'
244
+ ? 'latin1'
245
+ : 'utf8'
246
+
247
+ const val = headers[n + 1].toString(encoding)
248
+
249
+ ret.push(key, val)
250
+ }
251
+ return ret
235
252
  }
236
253
 
237
254
  function isBuffer (buffer) {
@@ -11,6 +11,7 @@ const {
11
11
  isValidHeaderValue
12
12
  } = require('./util')
13
13
  const { webidl } = require('./webidl')
14
+ const assert = require('assert')
14
15
 
15
16
  const kHeadersMap = Symbol('headers map')
16
17
  const kHeadersSortedMap = Symbol('headers map sorted')
@@ -23,10 +24,12 @@ function headerValueNormalize (potentialValue) {
23
24
  // To normalize a byte sequence potentialValue, remove
24
25
  // any leading and trailing HTTP whitespace bytes from
25
26
  // potentialValue.
26
- return potentialValue.replace(
27
- /^[\r\n\t ]+|[\r\n\t ]+$/g,
28
- ''
29
- )
27
+
28
+ // Trimming the end with `.replace()` and a RegExp is typically subject to
29
+ // ReDoS. This is safer and faster.
30
+ let i = potentialValue.length
31
+ while (/[\r\n\t ]/.test(potentialValue.charAt(--i)));
32
+ return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '')
30
33
  }
31
34
 
32
35
  function fill (headers, object) {
@@ -115,7 +118,7 @@ class HeadersList {
115
118
 
116
119
  if (lowercaseName === 'set-cookie') {
117
120
  this.cookies ??= []
118
- this.cookies.push([name, value])
121
+ this.cookies.push(value)
119
122
  }
120
123
  }
121
124
 
@@ -125,7 +128,7 @@ class HeadersList {
125
128
  const lowercaseName = name.toLowerCase()
126
129
 
127
130
  if (lowercaseName === 'set-cookie') {
128
- this.cookies = [[name, value]]
131
+ this.cookies = [value]
129
132
  }
130
133
 
131
134
  // 1. If list contains name, then set the value of
@@ -383,18 +386,74 @@ class Headers {
383
386
  return this[kHeadersList].set(name, value)
384
387
  }
385
388
 
389
+ // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
390
+ getSetCookie () {
391
+ webidl.brandCheck(this, Headers)
392
+
393
+ // 1. If this’s header list does not contain `Set-Cookie`, then return « ».
394
+ // 2. Return the values of all headers in this’s header list whose name is
395
+ // a byte-case-insensitive match for `Set-Cookie`, in order.
396
+
397
+ const list = this[kHeadersList].cookies
398
+
399
+ if (list) {
400
+ return [...list]
401
+ }
402
+
403
+ return []
404
+ }
405
+
406
+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
386
407
  get [kHeadersSortedMap] () {
387
- if (!this[kHeadersList][kHeadersSortedMap]) {
388
- this[kHeadersList][kHeadersSortedMap] = new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1))
408
+ if (this[kHeadersList][kHeadersSortedMap]) {
409
+ return this[kHeadersList][kHeadersSortedMap]
389
410
  }
390
- return this[kHeadersList][kHeadersSortedMap]
411
+
412
+ // 1. Let headers be an empty list of headers with the key being the name
413
+ // and value the value.
414
+ const headers = []
415
+
416
+ // 2. Let names be the result of convert header names to a sorted-lowercase
417
+ // set with all the names of the headers in list.
418
+ const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1)
419
+ const cookies = this[kHeadersList].cookies
420
+
421
+ // 3. For each name of names:
422
+ for (const [name, value] of names) {
423
+ // 1. If name is `set-cookie`, then:
424
+ if (name === 'set-cookie') {
425
+ // 1. Let values be a list of all values of headers in list whose name
426
+ // is a byte-case-insensitive match for name, in order.
427
+
428
+ // 2. For each value of values:
429
+ // 1. Append (name, value) to headers.
430
+ for (const value of cookies) {
431
+ headers.push([name, value])
432
+ }
433
+ } else {
434
+ // 2. Otherwise:
435
+
436
+ // 1. Let value be the result of getting name from list.
437
+
438
+ // 2. Assert: value is non-null.
439
+ assert(value !== null)
440
+
441
+ // 3. Append (name, value) to headers.
442
+ headers.push([name, value])
443
+ }
444
+ }
445
+
446
+ this[kHeadersList][kHeadersSortedMap] = headers
447
+
448
+ // 4. Return headers.
449
+ return headers
391
450
  }
392
451
 
393
452
  keys () {
394
453
  webidl.brandCheck(this, Headers)
395
454
 
396
455
  return makeIterator(
397
- () => [...this[kHeadersSortedMap].entries()],
456
+ () => [...this[kHeadersSortedMap].values()],
398
457
  'Headers',
399
458
  'key'
400
459
  )
@@ -404,7 +463,7 @@ class Headers {
404
463
  webidl.brandCheck(this, Headers)
405
464
 
406
465
  return makeIterator(
407
- () => [...this[kHeadersSortedMap].entries()],
466
+ () => [...this[kHeadersSortedMap].values()],
408
467
  'Headers',
409
468
  'value'
410
469
  )
@@ -414,7 +473,7 @@ class Headers {
414
473
  webidl.brandCheck(this, Headers)
415
474
 
416
475
  return makeIterator(
417
- () => [...this[kHeadersSortedMap].entries()],
476
+ () => [...this[kHeadersSortedMap].values()],
418
477
  'Headers',
419
478
  'key+value'
420
479
  )
@@ -28,6 +28,7 @@ const { getGlobalOrigin } = require('./global')
28
28
  const { URLSerializer } = require('./dataURL')
29
29
  const { kHeadersList } = require('../core/symbols')
30
30
  const assert = require('assert')
31
+ const { setMaxListeners, getEventListeners, defaultMaxListeners } = require('events')
31
32
 
32
33
  let TransformStream = globalThis.TransformStream
33
34
 
@@ -352,6 +353,11 @@ class Request {
352
353
  const abort = function () {
353
354
  acRef.deref()?.abort(this.reason)
354
355
  }
356
+
357
+ if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
358
+ setMaxListeners(100, signal)
359
+ }
360
+
355
361
  signal.addEventListener('abort', abort, { once: true })
356
362
  requestFinalizer.register(this, { signal, abort })
357
363
  }
@@ -188,7 +188,11 @@ function buildKey (opts) {
188
188
  }
189
189
 
190
190
  function generateKeyValues (data) {
191
- return Object.entries(data).reduce((keyValuePairs, [key, value]) => [...keyValuePairs, key, value], [])
191
+ return Object.entries(data).reduce((keyValuePairs, [key, value]) => [
192
+ ...keyValuePairs,
193
+ Buffer.from(`${key}`),
194
+ Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`)
195
+ ], [])
192
196
  }
193
197
 
194
198
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "5.18.0",
3
+ "version": "5.19.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -10,6 +10,8 @@ declare namespace buildConnector {
10
10
  socketPath?: string | null;
11
11
  timeout?: number | null;
12
12
  port?: number;
13
+ keepAlive?: boolean | null;
14
+ keepAliveInitialDelay?: number | null;
13
15
  }
14
16
 
15
17
  export interface Options {
package/types/fetch.d.ts CHANGED
@@ -59,6 +59,7 @@ export declare class Headers implements SpecIterable<[string, string]> {
59
59
  readonly get: (name: string) => string | null
60
60
  readonly has: (name: string) => boolean
61
61
  readonly set: (name: string, value: string) => void
62
+ readonly getSetCookie: () => string[]
62
63
  readonly forEach: (
63
64
  callbackfn: (value: string, key: string, iterable: Headers) => void,
64
65
  thisArg?: unknown