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.
- package/docs/api/Dispatcher.md +1 -0
- package/docs/api/Util.md +25 -0
- package/index.js +4 -0
- package/lib/agent.js +2 -0
- package/lib/api/readable.js +87 -70
- package/lib/client.js +24 -13
- package/lib/core/connect.js +1 -1
- package/lib/core/constants.js +2 -0
- package/lib/core/errors.js +10 -20
- package/lib/core/request.js +23 -49
- package/lib/core/tree.js +134 -0
- package/lib/core/util.js +86 -33
- package/lib/fetch/body.js +1 -1
- package/lib/fetch/dataURL.js +49 -44
- package/lib/fetch/headers.js +45 -27
- package/lib/fetch/index.js +96 -82
- package/lib/fetch/response.js +3 -3
- package/lib/fetch/util.js +51 -62
- package/package.json +2 -1
- package/types/dispatcher.d.ts +2 -0
- package/types/index.d.ts +1 -0
- package/types/util.d.ts +31 -0
package/lib/core/tree.js
ADDED
|
@@ -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
|
-
|
|
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]
|
|
228
|
-
|
|
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[
|
|
260
|
+
obj[key] = headersValue
|
|
235
261
|
} else {
|
|
236
|
-
obj[
|
|
262
|
+
obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
|
|
237
263
|
}
|
|
238
264
|
} else {
|
|
239
|
-
if (
|
|
265
|
+
if (typeof val === 'string') {
|
|
240
266
|
val = [val]
|
|
241
|
-
obj[
|
|
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
|
|
377
|
+
for (const [key, value] of this.headers) headers[key] = value
|
|
378
378
|
|
|
379
379
|
const responseFormData = new FormData()
|
|
380
380
|
|
package/lib/fetch/dataURL.js
CHANGED
|
@@ -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 = /
|
|
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 {
|
|
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 <
|
|
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
|
|
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
|
-
|
|
219
|
+
!(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
|
|
214
220
|
) {
|
|
215
|
-
output
|
|
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
|
|
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
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
|
439
|
-
|
|
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 {
|
|
579
|
+
* @param {number} char
|
|
574
580
|
*/
|
|
575
581
|
function isHTTPWhiteSpace (char) {
|
|
576
|
-
|
|
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
|
|
585
|
-
let trail = str.length - 1
|
|
586
|
-
|
|
593
|
+
let i = 0; let j = str.length
|
|
587
594
|
if (leading) {
|
|
588
|
-
|
|
595
|
+
while (j > i && isHTTPWhiteSpace(str.charCodeAt(i))) --i
|
|
589
596
|
}
|
|
590
|
-
|
|
591
597
|
if (trailing) {
|
|
592
|
-
|
|
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 {
|
|
605
|
+
* @param {number} char
|
|
601
606
|
*/
|
|
602
607
|
function isASCIIWhitespace (char) {
|
|
603
|
-
|
|
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
|
|
611
|
-
let trail = str.length - 1
|
|
612
|
-
|
|
619
|
+
let i = 0; let j = str.length
|
|
613
620
|
if (leading) {
|
|
614
|
-
|
|
621
|
+
while (j > i && isASCIIWhitespace(str.charCodeAt(i))) --i
|
|
615
622
|
}
|
|
616
|
-
|
|
617
623
|
if (trailing) {
|
|
618
|
-
|
|
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 = {
|
package/lib/fetch/headers.js
CHANGED
|
@@ -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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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
|