undici 6.0.0 → 6.1.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.
@@ -0,0 +1,134 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ wellknownHeaderNames,
5
+ headerNameLowerCasedRecord
6
+ } = require('./constants')
7
+
8
+ class TstNode {
9
+ /** @type {any} */
10
+ value = null
11
+ /** @type {null | TstNode} */
12
+ left = null
13
+ /** @type {null | TstNode} */
14
+ middle = null
15
+ /** @type {null | TstNode} */
16
+ right = null
17
+ /** @type {number} */
18
+ code
19
+ /**
20
+ * @param {Uint8Array} key
21
+ * @param {any} value
22
+ * @param {number} index
23
+ */
24
+ constructor (key, value, index) {
25
+ if (index === undefined || index >= key.length) {
26
+ throw new TypeError('Unreachable')
27
+ }
28
+ this.code = key[index]
29
+ if (key.length !== ++index) {
30
+ this.middle = new TstNode(key, value, index)
31
+ } else {
32
+ this.value = value
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {Uint8Array} key
38
+ * @param {any} value
39
+ * @param {number} index
40
+ */
41
+ add (key, value, index) {
42
+ if (index === undefined || index >= key.length) {
43
+ throw new TypeError('Unreachable')
44
+ }
45
+ const code = key[index]
46
+ if (this.code === code) {
47
+ if (key.length === ++index) {
48
+ this.value = value
49
+ } else if (this.middle !== null) {
50
+ this.middle.add(key, value, index)
51
+ } else {
52
+ this.middle = new TstNode(key, value, index)
53
+ }
54
+ } else if (this.code < code) {
55
+ if (this.left !== null) {
56
+ this.left.add(key, value, index)
57
+ } else {
58
+ this.left = new TstNode(key, value, index)
59
+ }
60
+ } else {
61
+ if (this.right !== null) {
62
+ this.right.add(key, value, index)
63
+ } else {
64
+ this.right = new TstNode(key, value, index)
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * @param {Uint8Array} key
71
+ * @return {TstNode | null}
72
+ */
73
+ search (key) {
74
+ const keylength = key.length
75
+ let index = 0
76
+ let node = this
77
+ while (node !== null && index < keylength) {
78
+ let code = key[index]
79
+ // A-Z
80
+ if (code >= 0x41 && code <= 0x5a) {
81
+ // Lowercase for uppercase.
82
+ code |= 32
83
+ }
84
+ while (node !== null) {
85
+ if (code === node.code) {
86
+ if (keylength === ++index) {
87
+ // Returns Node since it is the last key.
88
+ return node
89
+ }
90
+ node = node.middle
91
+ break
92
+ }
93
+ node = node.code < code ? node.left : node.right
94
+ }
95
+ }
96
+ return null
97
+ }
98
+ }
99
+
100
+ class TernarySearchTree {
101
+ /** @type {TstNode | null} */
102
+ node = null
103
+
104
+ /**
105
+ * @param {Uint8Array} key
106
+ * @param {any} value
107
+ * */
108
+ insert (key, value) {
109
+ if (this.node === null) {
110
+ this.node = new TstNode(key, value, 0)
111
+ } else {
112
+ this.node.add(key, value, 0)
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @param {Uint8Array} key
118
+ */
119
+ lookup (key) {
120
+ return this.node?.search(key)?.value ?? null
121
+ }
122
+ }
123
+
124
+ const tree = new TernarySearchTree()
125
+
126
+ for (let i = 0; i < wellknownHeaderNames.length; ++i) {
127
+ const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]
128
+ tree.insert(Buffer.from(key), key)
129
+ }
130
+
131
+ module.exports = {
132
+ TernarySearchTree,
133
+ tree
134
+ }
package/lib/core/util.js CHANGED
@@ -10,6 +10,7 @@ const { Blob } = require('buffer')
10
10
  const nodeUtil = require('util')
11
11
  const { stringify } = require('querystring')
12
12
  const { headerNameLowerCasedRecord } = require('./constants')
13
+ const { tree } = require('./tree')
13
14
 
14
15
  const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
15
16
 
@@ -219,26 +220,51 @@ function parseKeepAliveTimeout (val) {
219
220
  return m ? parseInt(m[1], 10) * 1000 : null
220
221
  }
221
222
 
222
- function parseHeaders (headers, obj = {}) {
223
+ /**
224
+ * Retrieves a header name and returns its lowercase value.
225
+ * @param {string | Buffer} value Header name
226
+ * @returns {string}
227
+ */
228
+ function headerNameToString (value) {
229
+ return typeof value === 'string'
230
+ ? headerNameLowerCasedRecord[value] ?? value.toLowerCase()
231
+ : tree.lookup(value) ?? value.toString('latin1').toLowerCase()
232
+ }
233
+
234
+ /**
235
+ * Receive the buffer as a string and return its lowercase value.
236
+ * @param {Buffer} value Header name
237
+ * @returns {string}
238
+ */
239
+ function bufferToLowerCasedHeaderName (value) {
240
+ return tree.lookup(value) ?? value.toString('latin1').toLowerCase()
241
+ }
242
+
243
+ /**
244
+ * @param {Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]} headers
245
+ * @param {Record<string, string | string[]>} [obj]
246
+ * @returns {Record<string, string | string[]>}
247
+ */
248
+ function parseHeaders (headers, obj) {
223
249
  // For H2 support
224
250
  if (!Array.isArray(headers)) return headers
225
251
 
252
+ if (obj === undefined) obj = {}
226
253
  for (let i = 0; i < headers.length; i += 2) {
227
- const key = headers[i].toString()
228
- const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase()
229
- let val = obj[lowerCasedKey]
254
+ const key = headerNameToString(headers[i])
255
+ let val = obj[key]
230
256
 
231
257
  if (!val) {
232
258
  const headersValue = headers[i + 1]
233
259
  if (typeof headersValue === 'string') {
234
- obj[lowerCasedKey] = headersValue
260
+ obj[key] = headersValue
235
261
  } else {
236
- obj[lowerCasedKey] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
262
+ obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
237
263
  }
238
264
  } else {
239
- if (!Array.isArray(val)) {
265
+ if (typeof val === 'string') {
240
266
  val = [val]
241
- obj[lowerCasedKey] = val
267
+ obj[key] = val
242
268
  }
243
269
  val.push(headers[i + 1].toString('utf8'))
244
270
  }
@@ -334,19 +360,11 @@ function isDisturbed (body) {
334
360
  }
335
361
 
336
362
  function isErrored (body) {
337
- return !!(body && (
338
- stream.isErrored
339
- ? stream.isErrored(body)
340
- : /state: 'errored'/.test(nodeUtil.inspect(body)
341
- )))
363
+ return !!(body && stream.isErrored(body))
342
364
  }
343
365
 
344
366
  function isReadable (body) {
345
- return !!(body && (
346
- stream.isReadable
347
- ? stream.isReadable(body)
348
- : /state: 'readable'/.test(nodeUtil.inspect(body)
349
- )))
367
+ return !!(body && stream.isReadable(body))
350
368
  }
351
369
 
352
370
  function getSocketInfo (socket) {
@@ -411,20 +429,6 @@ function isFormDataLike (object) {
411
429
  )
412
430
  }
413
431
 
414
- function throwIfAborted (signal) {
415
- if (!signal) { return }
416
- if (typeof signal.throwIfAborted === 'function') {
417
- signal.throwIfAborted()
418
- } else {
419
- if (signal.aborted) {
420
- // DOMException not available < v17.0.0
421
- const err = new Error('The operation was aborted')
422
- err.name = 'AbortError'
423
- throw err
424
- }
425
- }
426
- }
427
-
428
432
  function addAbortListener (signal, listener) {
429
433
  if ('addEventListener' in signal) {
430
434
  signal.addEventListener('abort', listener, { once: true })
@@ -449,6 +453,52 @@ function toUSVString (val) {
449
453
  return `${val}`
450
454
  }
451
455
 
456
+ /**
457
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
458
+ * @param {number} c
459
+ */
460
+ function isTokenCharCode (c) {
461
+ switch (c) {
462
+ case 0x22:
463
+ case 0x28:
464
+ case 0x29:
465
+ case 0x2c:
466
+ case 0x2f:
467
+ case 0x3a:
468
+ case 0x3b:
469
+ case 0x3c:
470
+ case 0x3d:
471
+ case 0x3e:
472
+ case 0x3f:
473
+ case 0x40:
474
+ case 0x5b:
475
+ case 0x5c:
476
+ case 0x5d:
477
+ case 0x7b:
478
+ case 0x7d:
479
+ // DQUOTE and "(),/:;<=>?@[\]{}"
480
+ return false
481
+ default:
482
+ // VCHAR %x21-7E
483
+ return c >= 0x21 && c <= 0x7e
484
+ }
485
+ }
486
+
487
+ /**
488
+ * @param {string} characters
489
+ */
490
+ function isValidHTTPToken (characters) {
491
+ if (characters.length === 0) {
492
+ return false
493
+ }
494
+ for (let i = 0; i < characters.length; ++i) {
495
+ if (!isTokenCharCode(characters.charCodeAt(i))) {
496
+ return false
497
+ }
498
+ }
499
+ return true
500
+ }
501
+
452
502
  // Parsed accordingly to RFC 9110
453
503
  // https://www.rfc-editor.org/rfc/rfc9110#field.content-range
454
504
  function parseRangeHeader (range) {
@@ -483,6 +533,8 @@ module.exports = {
483
533
  isIterable,
484
534
  isAsyncIterable,
485
535
  isDestroyed,
536
+ headerNameToString,
537
+ bufferToLowerCasedHeaderName,
486
538
  parseRawHeaders,
487
539
  parseHeaders,
488
540
  parseKeepAliveTimeout,
@@ -495,8 +547,9 @@ module.exports = {
495
547
  getSocketInfo,
496
548
  isFormDataLike,
497
549
  buildURL,
498
- throwIfAborted,
499
550
  addAbortListener,
551
+ isValidHTTPToken,
552
+ isTokenCharCode,
500
553
  parseRangeHeader,
501
554
  nodeMajor,
502
555
  nodeMinor,
package/lib/fetch/body.js CHANGED
@@ -374,7 +374,7 @@ function bodyMixinMethods (instance) {
374
374
  // If mimeType’s essence is "multipart/form-data", then:
375
375
  if (/multipart\/form-data/.test(contentType)) {
376
376
  const headers = {}
377
- for (const [key, value] of this.headers) headers[key.toLowerCase()] = value
377
+ for (const [key, value] of this.headers) headers[key] = value
378
378
 
379
379
  const responseFormData = new FormData()
380
380
 
@@ -1,5 +1,4 @@
1
1
  const assert = require('assert')
2
- const { atob } = require('buffer')
3
2
  const { isomorphicDecode } = require('./util')
4
3
 
5
4
  const encoder = new TextEncoder()
@@ -8,7 +7,8 @@ const encoder = new TextEncoder()
8
7
  * @see https://mimesniff.spec.whatwg.org/#http-token-code-point
9
8
  */
10
9
  const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/
11
- const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line
10
+ const HTTP_WHITESPACE_REGEX = /[\u000A|\u000D|\u0009|\u0020]/ // eslint-disable-line
11
+ const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
12
12
  /**
13
13
  * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
14
14
  */
@@ -188,20 +188,26 @@ function stringPercentDecode (input) {
188
188
  return percentDecode(bytes)
189
189
  }
190
190
 
191
+ function isHexCharByte (byte) {
192
+ // 0-9 A-F a-f
193
+ return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) || (byte >= 0x61 && byte <= 0x66)
194
+ }
195
+
191
196
  // https://url.spec.whatwg.org/#percent-decode
192
197
  /** @param {Uint8Array} input */
193
198
  function percentDecode (input) {
199
+ const length = input.length
194
200
  // 1. Let output be an empty byte sequence.
195
- /** @type {number[]} */
196
- const output = []
197
-
201
+ /** @type {Uint8Array} */
202
+ const output = new Uint8Array(length)
203
+ let j = 0
198
204
  // 2. For each byte byte in input:
199
- for (let i = 0; i < input.length; i++) {
205
+ for (let i = 0; i < length; ++i) {
200
206
  const byte = input[i]
201
207
 
202
208
  // 1. If byte is not 0x25 (%), then append byte to output.
203
209
  if (byte !== 0x25) {
204
- output.push(byte)
210
+ output[j++] = byte
205
211
 
206
212
  // 2. Otherwise, if byte is 0x25 (%) and the next two bytes
207
213
  // after byte in input are not in the ranges
@@ -210,9 +216,9 @@ function percentDecode (input) {
210
216
  // to output.
211
217
  } else if (
212
218
  byte === 0x25 &&
213
- !/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2]))
219
+ !(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
214
220
  ) {
215
- output.push(0x25)
221
+ output[j++] = 0x25
216
222
 
217
223
  // 3. Otherwise:
218
224
  } else {
@@ -222,7 +228,7 @@ function percentDecode (input) {
222
228
  const bytePoint = Number.parseInt(nextTwoBytes, 16)
223
229
 
224
230
  // 2. Append a byte whose value is bytePoint to output.
225
- output.push(bytePoint)
231
+ output[j++] = bytePoint
226
232
 
227
233
  // 3. Skip the next two bytes in input.
228
234
  i += 2
@@ -230,7 +236,7 @@ function percentDecode (input) {
230
236
  }
231
237
 
232
238
  // 3. Return output.
233
- return Uint8Array.from(output)
239
+ return length === j ? output : output.subarray(0, j)
234
240
  }
235
241
 
236
242
  // https://mimesniff.spec.whatwg.org/#parse-a-mime-type
@@ -410,19 +416,25 @@ function parseMIMEType (input) {
410
416
  /** @param {string} data */
411
417
  function forgivingBase64 (data) {
412
418
  // 1. Remove all ASCII whitespace from data.
413
- data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line
419
+ data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '') // eslint-disable-line
414
420
 
421
+ let dataLength = data.length
415
422
  // 2. If data’s code point length divides by 4 leaving
416
423
  // no remainder, then:
417
- if (data.length % 4 === 0) {
424
+ if (dataLength % 4 === 0) {
418
425
  // 1. If data ends with one or two U+003D (=) code points,
419
426
  // then remove them from data.
420
- data = data.replace(/=?=$/, '')
427
+ if (data.charCodeAt(dataLength - 1) === 0x003D) {
428
+ --dataLength
429
+ if (data.charCodeAt(dataLength - 1) === 0x003D) {
430
+ --dataLength
431
+ }
432
+ }
421
433
  }
422
434
 
423
435
  // 3. If data’s code point length divides by 4 leaving
424
436
  // a remainder of 1, then return failure.
425
- if (data.length % 4 === 1) {
437
+ if (dataLength % 4 === 1) {
426
438
  return 'failure'
427
439
  }
428
440
 
@@ -431,18 +443,12 @@ function forgivingBase64 (data) {
431
443
  // U+002F (/)
432
444
  // ASCII alphanumeric
433
445
  // then return failure.
434
- if (/[^+/0-9A-Za-z]/.test(data)) {
446
+ if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
435
447
  return 'failure'
436
448
  }
437
449
 
438
- const binary = atob(data)
439
- const bytes = new Uint8Array(binary.length)
440
-
441
- for (let byte = 0; byte < binary.length; byte++) {
442
- bytes[byte] = binary.charCodeAt(byte)
443
- }
444
-
445
- return bytes
450
+ const buffer = Buffer.from(data, 'base64')
451
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
446
452
  }
447
453
 
448
454
  // https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
@@ -570,55 +576,54 @@ function serializeAMimeType (mimeType) {
570
576
 
571
577
  /**
572
578
  * @see https://fetch.spec.whatwg.org/#http-whitespace
573
- * @param {string} char
579
+ * @param {number} char
574
580
  */
575
581
  function isHTTPWhiteSpace (char) {
576
- return char === '\r' || char === '\n' || char === '\t' || char === ' '
582
+ // "\r\n\t "
583
+ return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x020
577
584
  }
578
585
 
579
586
  /**
580
587
  * @see https://fetch.spec.whatwg.org/#http-whitespace
581
588
  * @param {string} str
589
+ * @param {boolean} [leading=true]
590
+ * @param {boolean} [trailing=true]
582
591
  */
583
592
  function removeHTTPWhitespace (str, leading = true, trailing = true) {
584
- let lead = 0
585
- let trail = str.length - 1
586
-
593
+ let i = 0; let j = str.length
587
594
  if (leading) {
588
- for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++);
595
+ while (j > i && isHTTPWhiteSpace(str.charCodeAt(i))) --i
589
596
  }
590
-
591
597
  if (trailing) {
592
- for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--);
598
+ while (j > i && isHTTPWhiteSpace(str.charCodeAt(j - 1))) --j
593
599
  }
594
-
595
- return str.slice(lead, trail + 1)
600
+ return i === 0 && j === str.length ? str : str.substring(i, j)
596
601
  }
597
602
 
598
603
  /**
599
604
  * @see https://infra.spec.whatwg.org/#ascii-whitespace
600
- * @param {string} char
605
+ * @param {number} char
601
606
  */
602
607
  function isASCIIWhitespace (char) {
603
- return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' '
608
+ // "\r\n\t\f "
609
+ return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x00c || char === 0x020
604
610
  }
605
611
 
606
612
  /**
607
613
  * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
614
+ * @param {string} str
615
+ * @param {boolean} [leading=true]
616
+ * @param {boolean} [trailing=true]
608
617
  */
609
618
  function removeASCIIWhitespace (str, leading = true, trailing = true) {
610
- let lead = 0
611
- let trail = str.length - 1
612
-
619
+ let i = 0; let j = str.length
613
620
  if (leading) {
614
- for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++);
621
+ while (j > i && isASCIIWhitespace(str.charCodeAt(i))) --i
615
622
  }
616
-
617
623
  if (trailing) {
618
- for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--);
624
+ while (j > i && isASCIIWhitespace(str.charCodeAt(j - 1))) --j
619
625
  }
620
-
621
- return str.slice(lead, trail + 1)
626
+ return i === 0 && j === str.length ? str : str.substring(i, j)
622
627
  }
623
628
 
624
629
  module.exports = {
@@ -114,7 +114,7 @@ function appendHeader (headers, name, value) {
114
114
  // forbidden response-header name, return.
115
115
 
116
116
  // 7. Append (name, value) to headers’s header list.
117
- return headers[kHeadersList].append(name, value)
117
+ return headers[kHeadersList].append(name, value, false)
118
118
 
119
119
  // 8. If headers’s guard is "request-no-cors", then remove
120
120
  // privileged no-CORS request headers from headers
@@ -135,14 +135,17 @@ class HeadersList {
135
135
  }
136
136
  }
137
137
 
138
- // https://fetch.spec.whatwg.org/#header-list-contains
139
- contains (name) {
138
+ /**
139
+ * @see https://fetch.spec.whatwg.org/#header-list-contains
140
+ * @param {string} name
141
+ * @param {boolean} isLowerCase
142
+ */
143
+ contains (name, isLowerCase) {
140
144
  // A header list list contains a header name name if list
141
145
  // contains a header whose name is a byte-case-insensitive
142
146
  // match for name.
143
- name = name.toLowerCase()
144
147
 
145
- return this[kHeadersMap].has(name)
148
+ return this[kHeadersMap].has(isLowerCase ? name : name.toLowerCase())
146
149
  }
147
150
 
148
151
  clear () {
@@ -151,13 +154,18 @@ class HeadersList {
151
154
  this.cookies = null
152
155
  }
153
156
 
154
- // https://fetch.spec.whatwg.org/#concept-header-list-append
155
- append (name, value) {
157
+ /**
158
+ * @see https://fetch.spec.whatwg.org/#concept-header-list-append
159
+ * @param {string} name
160
+ * @param {string} value
161
+ * @param {boolean} isLowerCase
162
+ */
163
+ append (name, value, isLowerCase) {
156
164
  this[kHeadersSortedMap] = null
157
165
 
158
166
  // 1. If list contains name, then set name to the first such
159
167
  // header’s name.
160
- const lowercaseName = name.toLowerCase()
168
+ const lowercaseName = isLowerCase ? name : name.toLowerCase()
161
169
  const exists = this[kHeadersMap].get(lowercaseName)
162
170
 
163
171
  // 2. Append (name, value) to list.
@@ -172,15 +180,19 @@ class HeadersList {
172
180
  }
173
181
 
174
182
  if (lowercaseName === 'set-cookie') {
175
- this.cookies ??= []
176
- this.cookies.push(value)
183
+ (this.cookies ??= []).push(value)
177
184
  }
178
185
  }
179
186
 
180
- // https://fetch.spec.whatwg.org/#concept-header-list-set
181
- set (name, value) {
187
+ /**
188
+ * @see https://fetch.spec.whatwg.org/#concept-header-list-set
189
+ * @param {string} name
190
+ * @param {string} value
191
+ * @param {boolean} isLowerCase
192
+ */
193
+ set (name, value, isLowerCase) {
182
194
  this[kHeadersSortedMap] = null
183
- const lowercaseName = name.toLowerCase()
195
+ const lowercaseName = isLowerCase ? name : name.toLowerCase()
184
196
 
185
197
  if (lowercaseName === 'set-cookie') {
186
198
  this.cookies = [value]
@@ -193,11 +205,14 @@ class HeadersList {
193
205
  this[kHeadersMap].set(lowercaseName, { name, value })
194
206
  }
195
207
 
196
- // https://fetch.spec.whatwg.org/#concept-header-list-delete
197
- delete (name) {
208
+ /**
209
+ * @see https://fetch.spec.whatwg.org/#concept-header-list-delete
210
+ * @param {string} name
211
+ * @param {boolean} isLowerCase
212
+ */
213
+ delete (name, isLowerCase) {
198
214
  this[kHeadersSortedMap] = null
199
-
200
- name = name.toLowerCase()
215
+ if (!isLowerCase) name = name.toLowerCase()
201
216
 
202
217
  if (name === 'set-cookie') {
203
218
  this.cookies = null
@@ -206,15 +221,18 @@ class HeadersList {
206
221
  this[kHeadersMap].delete(name)
207
222
  }
208
223
 
209
- // https://fetch.spec.whatwg.org/#concept-header-list-get
210
- get (name) {
211
- const value = this[kHeadersMap].get(name.toLowerCase())
212
-
224
+ /**
225
+ * @see https://fetch.spec.whatwg.org/#concept-header-list-get
226
+ * @param {string} name
227
+ * @param {boolean} isLowerCase
228
+ * @returns {string | null}
229
+ */
230
+ get (name, isLowerCase) {
213
231
  // 1. If list does not contain name, then return null.
214
232
  // 2. Return the values of all headers in list whose name
215
233
  // is a byte-case-insensitive match for name,
216
234
  // separated from each other by 0x2C 0x20, in order.
217
- return value === undefined ? null : value.value
235
+ return this[kHeadersMap].get(isLowerCase ? name : name.toLowerCase())?.value ?? null
218
236
  }
219
237
 
220
238
  * [Symbol.iterator] () {
@@ -304,14 +322,14 @@ class Headers {
304
322
 
305
323
  // 6. If this’s header list does not contain name, then
306
324
  // return.
307
- if (!this[kHeadersList].contains(name)) {
325
+ if (!this[kHeadersList].contains(name, false)) {
308
326
  return
309
327
  }
310
328
 
311
329
  // 7. Delete name from this’s header list.
312
330
  // 8. If this’s guard is "request-no-cors", then remove
313
331
  // privileged no-CORS request headers from this.
314
- this[kHeadersList].delete(name)
332
+ this[kHeadersList].delete(name, false)
315
333
  }
316
334
 
317
335
  // https://fetch.spec.whatwg.org/#dom-headers-get
@@ -333,7 +351,7 @@ class Headers {
333
351
 
334
352
  // 2. Return the result of getting name from this’s header
335
353
  // list.
336
- return this[kHeadersList].get(name)
354
+ return this[kHeadersList].get(name, false)
337
355
  }
338
356
 
339
357
  // https://fetch.spec.whatwg.org/#dom-headers-has
@@ -355,7 +373,7 @@ class Headers {
355
373
 
356
374
  // 2. Return true if this’s header list contains name;
357
375
  // otherwise false.
358
- return this[kHeadersList].contains(name)
376
+ return this[kHeadersList].contains(name, false)
359
377
  }
360
378
 
361
379
  // https://fetch.spec.whatwg.org/#dom-headers-set
@@ -404,7 +422,7 @@ class Headers {
404
422
  // 7. Set (name, value) in this’s header list.
405
423
  // 8. If this’s guard is "request-no-cors", then remove
406
424
  // privileged no-CORS request headers from this
407
- this[kHeadersList].set(name, value)
425
+ this[kHeadersList].set(name, value, false)
408
426
  }
409
427
 
410
428
  // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie