undici 5.27.2 → 5.28.0

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.
@@ -16,6 +16,13 @@ const assert = require('assert')
16
16
  const kHeadersMap = Symbol('headers map')
17
17
  const kHeadersSortedMap = Symbol('headers map sorted')
18
18
 
19
+ /**
20
+ * @param {number} code
21
+ */
22
+ function isHTTPWhiteSpaceCharCode (code) {
23
+ return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020
24
+ }
25
+
19
26
  /**
20
27
  * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
21
28
  * @param {string} potentialValue
@@ -24,12 +31,12 @@ function headerValueNormalize (potentialValue) {
24
31
  // To normalize a byte sequence potentialValue, remove
25
32
  // any leading and trailing HTTP whitespace bytes from
26
33
  // potentialValue.
34
+ let i = 0; let j = potentialValue.length
27
35
 
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 ]+/, '')
36
+ while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j
37
+ while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i
38
+
39
+ return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j)
33
40
  }
34
41
 
35
42
  function fill (headers, object) {
@@ -38,7 +45,8 @@ function fill (headers, object) {
38
45
  // 1. If object is a sequence, then for each header in object:
39
46
  // Note: webidl conversion to array has already been done.
40
47
  if (Array.isArray(object)) {
41
- for (const header of object) {
48
+ for (let i = 0; i < object.length; ++i) {
49
+ const header = object[i]
42
50
  // 1. If header does not contain exactly two items, then throw a TypeError.
43
51
  if (header.length !== 2) {
44
52
  throw webidl.errors.exception({
@@ -48,15 +56,16 @@ function fill (headers, object) {
48
56
  }
49
57
 
50
58
  // 2. Append (header’s first item, header’s second item) to headers.
51
- headers.append(header[0], header[1])
59
+ appendHeader(headers, header[0], header[1])
52
60
  }
53
61
  } else if (typeof object === 'object' && object !== null) {
54
62
  // Note: null should throw
55
63
 
56
64
  // 2. Otherwise, object is a record, then for each key → value in object,
57
65
  // append (key, value) to headers
58
- for (const [key, value] of Object.entries(object)) {
59
- headers.append(key, value)
66
+ const keys = Object.keys(object)
67
+ for (let i = 0; i < keys.length; ++i) {
68
+ appendHeader(headers, keys[i], object[keys[i]])
60
69
  }
61
70
  } else {
62
71
  throw webidl.errors.conversionFailed({
@@ -67,6 +76,50 @@ function fill (headers, object) {
67
76
  }
68
77
  }
69
78
 
79
+ /**
80
+ * @see https://fetch.spec.whatwg.org/#concept-headers-append
81
+ */
82
+ function appendHeader (headers, name, value) {
83
+ // 1. Normalize value.
84
+ value = headerValueNormalize(value)
85
+
86
+ // 2. If name is not a header name or value is not a
87
+ // header value, then throw a TypeError.
88
+ if (!isValidHeaderName(name)) {
89
+ throw webidl.errors.invalidArgument({
90
+ prefix: 'Headers.append',
91
+ value: name,
92
+ type: 'header name'
93
+ })
94
+ } else if (!isValidHeaderValue(value)) {
95
+ throw webidl.errors.invalidArgument({
96
+ prefix: 'Headers.append',
97
+ value,
98
+ type: 'header value'
99
+ })
100
+ }
101
+
102
+ // 3. If headers’s guard is "immutable", then throw a TypeError.
103
+ // 4. Otherwise, if headers’s guard is "request" and name is a
104
+ // forbidden header name, return.
105
+ // Note: undici does not implement forbidden header names
106
+ if (headers[kGuard] === 'immutable') {
107
+ throw new TypeError('immutable')
108
+ } else if (headers[kGuard] === 'request-no-cors') {
109
+ // 5. Otherwise, if headers’s guard is "request-no-cors":
110
+ // TODO
111
+ }
112
+
113
+ // 6. Otherwise, if headers’s guard is "response" and name is a
114
+ // forbidden response-header name, return.
115
+
116
+ // 7. Append (name, value) to headers’s header list.
117
+ return headers[kHeadersList].append(name, value)
118
+
119
+ // 8. If headers’s guard is "request-no-cors", then remove
120
+ // privileged no-CORS request headers from headers
121
+ }
122
+
70
123
  class HeadersList {
71
124
  /** @type {[string, string][]|null} */
72
125
  cookies = null
@@ -75,7 +128,7 @@ class HeadersList {
75
128
  if (init instanceof HeadersList) {
76
129
  this[kHeadersMap] = new Map(init[kHeadersMap])
77
130
  this[kHeadersSortedMap] = init[kHeadersSortedMap]
78
- this.cookies = init.cookies
131
+ this.cookies = init.cookies === null ? null : [...init.cookies]
79
132
  } else {
80
133
  this[kHeadersMap] = new Map(init)
81
134
  this[kHeadersSortedMap] = null
@@ -137,7 +190,7 @@ class HeadersList {
137
190
  // the first such header to value and remove the
138
191
  // others.
139
192
  // 2. Otherwise, append header (name, value) to list.
140
- return this[kHeadersMap].set(lowercaseName, { name, value })
193
+ this[kHeadersMap].set(lowercaseName, { name, value })
141
194
  }
142
195
 
143
196
  // https://fetch.spec.whatwg.org/#concept-header-list-delete
@@ -150,20 +203,18 @@ class HeadersList {
150
203
  this.cookies = null
151
204
  }
152
205
 
153
- return this[kHeadersMap].delete(name)
206
+ this[kHeadersMap].delete(name)
154
207
  }
155
208
 
156
209
  // https://fetch.spec.whatwg.org/#concept-header-list-get
157
210
  get (name) {
158
- // 1. If list does not contain name, then return null.
159
- if (!this.contains(name)) {
160
- return null
161
- }
211
+ const value = this[kHeadersMap].get(name.toLowerCase())
162
212
 
213
+ // 1. If list does not contain name, then return null.
163
214
  // 2. Return the values of all headers in list whose name
164
215
  // is a byte-case-insensitive match for name,
165
216
  // separated from each other by 0x2C 0x20, in order.
166
- return this[kHeadersMap].get(name.toLowerCase())?.value ?? null
217
+ return value === undefined ? null : value.value
167
218
  }
168
219
 
169
220
  * [Symbol.iterator] () {
@@ -212,43 +263,7 @@ class Headers {
212
263
  name = webidl.converters.ByteString(name)
213
264
  value = webidl.converters.ByteString(value)
214
265
 
215
- // 1. Normalize value.
216
- value = headerValueNormalize(value)
217
-
218
- // 2. If name is not a header name or value is not a
219
- // header value, then throw a TypeError.
220
- if (!isValidHeaderName(name)) {
221
- throw webidl.errors.invalidArgument({
222
- prefix: 'Headers.append',
223
- value: name,
224
- type: 'header name'
225
- })
226
- } else if (!isValidHeaderValue(value)) {
227
- throw webidl.errors.invalidArgument({
228
- prefix: 'Headers.append',
229
- value,
230
- type: 'header value'
231
- })
232
- }
233
-
234
- // 3. If headers’s guard is "immutable", then throw a TypeError.
235
- // 4. Otherwise, if headers’s guard is "request" and name is a
236
- // forbidden header name, return.
237
- // Note: undici does not implement forbidden header names
238
- if (this[kGuard] === 'immutable') {
239
- throw new TypeError('immutable')
240
- } else if (this[kGuard] === 'request-no-cors') {
241
- // 5. Otherwise, if headers’s guard is "request-no-cors":
242
- // TODO
243
- }
244
-
245
- // 6. Otherwise, if headers’s guard is "response" and name is a
246
- // forbidden response-header name, return.
247
-
248
- // 7. Append (name, value) to headers’s header list.
249
- // 8. If headers’s guard is "request-no-cors", then remove
250
- // privileged no-CORS request headers from headers
251
- return this[kHeadersList].append(name, value)
266
+ return appendHeader(this, name, value)
252
267
  }
253
268
 
254
269
  // https://fetch.spec.whatwg.org/#dom-headers-delete
@@ -293,7 +308,7 @@ class Headers {
293
308
  // 7. Delete name from this’s header list.
294
309
  // 8. If this’s guard is "request-no-cors", then remove
295
310
  // privileged no-CORS request headers from this.
296
- return this[kHeadersList].delete(name)
311
+ this[kHeadersList].delete(name)
297
312
  }
298
313
 
299
314
  // https://fetch.spec.whatwg.org/#dom-headers-get
@@ -386,7 +401,7 @@ class Headers {
386
401
  // 7. Set (name, value) in this’s header list.
387
402
  // 8. If this’s guard is "request-no-cors", then remove
388
403
  // privileged no-CORS request headers from this
389
- return this[kHeadersList].set(name, value)
404
+ this[kHeadersList].set(name, value)
390
405
  }
391
406
 
392
407
  // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
@@ -422,7 +437,8 @@ class Headers {
422
437
  const cookies = this[kHeadersList].cookies
423
438
 
424
439
  // 3. For each name of names:
425
- for (const [name, value] of names) {
440
+ for (let i = 0; i < names.length; ++i) {
441
+ const [name, value] = names[i]
426
442
  // 1. If name is `set-cookie`, then:
427
443
  if (name === 'set-cookie') {
428
444
  // 1. Let values be a list of all values of headers in list whose name
@@ -430,8 +446,8 @@ class Headers {
430
446
 
431
447
  // 2. For each value of values:
432
448
  // 1. Append (name, value) to headers.
433
- for (const value of cookies) {
434
- headers.push([name, value])
449
+ for (let j = 0; j < cookies.length; ++j) {
450
+ headers.push([name, cookies[j]])
435
451
  }
436
452
  } else {
437
453
  // 2. Otherwise:
@@ -455,6 +471,12 @@ class Headers {
455
471
  keys () {
456
472
  webidl.brandCheck(this, Headers)
457
473
 
474
+ if (this[kGuard] === 'immutable') {
475
+ const value = this[kHeadersSortedMap]
476
+ return makeIterator(() => value, 'Headers',
477
+ 'key')
478
+ }
479
+
458
480
  return makeIterator(
459
481
  () => [...this[kHeadersSortedMap].values()],
460
482
  'Headers',
@@ -465,6 +487,12 @@ class Headers {
465
487
  values () {
466
488
  webidl.brandCheck(this, Headers)
467
489
 
490
+ if (this[kGuard] === 'immutable') {
491
+ const value = this[kHeadersSortedMap]
492
+ return makeIterator(() => value, 'Headers',
493
+ 'value')
494
+ }
495
+
468
496
  return makeIterator(
469
497
  () => [...this[kHeadersSortedMap].values()],
470
498
  'Headers',
@@ -475,6 +503,12 @@ class Headers {
475
503
  entries () {
476
504
  webidl.brandCheck(this, Headers)
477
505
 
506
+ if (this[kGuard] === 'immutable') {
507
+ const value = this[kHeadersSortedMap]
508
+ return makeIterator(() => value, 'Headers',
509
+ 'key+value')
510
+ }
511
+
478
512
  return makeIterator(
479
513
  () => [...this[kHeadersSortedMap].values()],
480
514
  'Headers',
@@ -1957,7 +1957,7 @@ async function httpNetworkFetch (
1957
1957
  path: url.pathname + url.search,
1958
1958
  origin: url.origin,
1959
1959
  method: request.method,
1960
- body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
1960
+ body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
1961
1961
  headers: request.headersList.entries,
1962
1962
  maxRedirections: 0,
1963
1963
  upgrade: request.mode === 'websocket' ? 'websocket' : undefined
@@ -2002,7 +2002,7 @@ async function httpNetworkFetch (
2002
2002
  location = val
2003
2003
  }
2004
2004
 
2005
- headers.append(key, val)
2005
+ headers[kHeadersList].append(key, val)
2006
2006
  }
2007
2007
  } else {
2008
2008
  const keys = Object.keys(headersList)
@@ -2016,7 +2016,7 @@ async function httpNetworkFetch (
2016
2016
  location = val
2017
2017
  }
2018
2018
 
2019
- headers.append(key, val)
2019
+ headers[kHeadersList].append(key, val)
2020
2020
  }
2021
2021
  }
2022
2022
 
@@ -2120,7 +2120,7 @@ async function httpNetworkFetch (
2120
2120
  const key = headersList[n + 0].toString('latin1')
2121
2121
  const val = headersList[n + 1].toString('latin1')
2122
2122
 
2123
- headers.append(key, val)
2123
+ headers[kHeadersList].append(key, val)
2124
2124
  }
2125
2125
 
2126
2126
  resolve({
@@ -316,11 +316,11 @@ class Request {
316
316
  // 2. If method is not a method or method is a forbidden method, then
317
317
  // throw a TypeError.
318
318
  if (!isValidHTTPToken(init.method)) {
319
- throw TypeError(`'${init.method}' is not a valid HTTP method.`)
319
+ throw new TypeError(`'${init.method}' is not a valid HTTP method.`)
320
320
  }
321
321
 
322
322
  if (forbiddenMethodsSet.has(method.toUpperCase())) {
323
- throw TypeError(`'${init.method}' HTTP method is unsupported.`)
323
+ throw new TypeError(`'${init.method}' HTTP method is unsupported.`)
324
324
  }
325
325
 
326
326
  // 3. Normalize method.
package/lib/fetch/util.js CHANGED
@@ -103,52 +103,57 @@ function isValidReasonPhrase (statusText) {
103
103
  return true
104
104
  }
105
105
 
106
- function isTokenChar (c) {
107
- return !(
108
- c >= 0x7f ||
109
- c <= 0x20 ||
110
- c === '(' ||
111
- c === ')' ||
112
- c === '<' ||
113
- c === '>' ||
114
- c === '@' ||
115
- c === ',' ||
116
- c === ';' ||
117
- c === ':' ||
118
- c === '\\' ||
119
- c === '"' ||
120
- c === '/' ||
121
- c === '[' ||
122
- c === ']' ||
123
- c === '?' ||
124
- c === '=' ||
125
- c === '{' ||
126
- c === '}'
127
- )
106
+ /**
107
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
108
+ * @param {number} c
109
+ */
110
+ function isTokenCharCode (c) {
111
+ switch (c) {
112
+ case 0x22:
113
+ case 0x28:
114
+ case 0x29:
115
+ case 0x2c:
116
+ case 0x2f:
117
+ case 0x3a:
118
+ case 0x3b:
119
+ case 0x3c:
120
+ case 0x3d:
121
+ case 0x3e:
122
+ case 0x3f:
123
+ case 0x40:
124
+ case 0x5b:
125
+ case 0x5c:
126
+ case 0x5d:
127
+ case 0x7b:
128
+ case 0x7d:
129
+ // DQUOTE and "(),/:;<=>?@[\]{}"
130
+ return false
131
+ default:
132
+ // VCHAR %x21-7E
133
+ return c >= 0x21 && c <= 0x7e
134
+ }
128
135
  }
129
136
 
130
- // See RFC 7230, Section 3.2.6.
131
- // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
137
+ /**
138
+ * @param {string} characters
139
+ */
132
140
  function isValidHTTPToken (characters) {
133
- if (!characters || typeof characters !== 'string') {
141
+ if (characters.length === 0) {
134
142
  return false
135
143
  }
136
144
  for (let i = 0; i < characters.length; ++i) {
137
- const c = characters.charCodeAt(i)
138
- if (c > 0x7f || !isTokenChar(c)) {
145
+ if (!isTokenCharCode(characters.charCodeAt(i))) {
139
146
  return false
140
147
  }
141
148
  }
142
149
  return true
143
150
  }
144
151
 
145
- // https://fetch.spec.whatwg.org/#header-name
146
- // https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
152
+ /**
153
+ * @see https://fetch.spec.whatwg.org/#header-name
154
+ * @param {string} potentialValue
155
+ */
147
156
  function isValidHeaderName (potentialValue) {
148
- if (potentialValue.length === 0) {
149
- return false
150
- }
151
-
152
157
  return isValidHTTPToken(potentialValue)
153
158
  }
154
159
 
@@ -427,12 +427,10 @@ webidl.converters.ByteString = function (V) {
427
427
  // 2. If the value of any element of x is greater than
428
428
  // 255, then throw a TypeError.
429
429
  for (let index = 0; index < x.length; index++) {
430
- const charCode = x.charCodeAt(index)
431
-
432
- if (charCode > 255) {
430
+ if (x.charCodeAt(index) > 255) {
433
431
  throw new TypeError(
434
432
  'Cannot convert argument to a ByteString because the character at ' +
435
- `index ${index} has a value of ${charCode} which is greater than 255.`
433
+ `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
436
434
  )
437
435
  }
438
436
  }