undici 6.21.0 → 7.0.0-alpha.10
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/README.md +27 -46
- package/docs/docs/api/Agent.md +14 -17
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -14
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -194
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
- package/docs/docs/api/MockAgent.md +5 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +4 -3
- package/docs/docs/api/Pool.md +15 -16
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +4 -4
- package/docs/docs/api/WebSocket.md +46 -4
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +23 -7
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +7 -6
- package/lib/api/api-request.js +33 -48
- package/lib/api/api-stream.js +39 -50
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +235 -62
- package/lib/api/util.js +2 -0
- package/lib/cache/memory-cache-store.js +177 -0
- package/lib/cache/sqlite-cache-store.js +446 -0
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +6 -6
- package/lib/core/request.js +13 -11
- package/lib/core/symbols.js +2 -1
- package/lib/core/tree.js +9 -1
- package/lib/core/util.js +237 -49
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +379 -134
- package/lib/dispatcher/client-h2.js +173 -107
- package/lib/dispatcher/client.js +19 -32
- package/lib/dispatcher/dispatcher-base.js +6 -35
- package/lib/dispatcher/dispatcher.js +7 -24
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +27 -0
- package/lib/handler/redirect-handler.js +54 -59
- package/lib/handler/retry-handler.js +77 -109
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +350 -0
- package/lib/interceptor/dns.js +375 -0
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +18 -7
- package/lib/llhttp/constants.d.ts +97 -0
- package/lib/llhttp/constants.js +412 -192
- package/lib/llhttp/constants.js.map +1 -0
- package/lib/llhttp/llhttp-wasm.js +11 -1
- package/lib/llhttp/llhttp_simd-wasm.js +11 -1
- package/lib/llhttp/utils.d.ts +2 -0
- package/lib/llhttp/utils.js +9 -9
- package/lib/llhttp/utils.js.map +1 -0
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +9 -4
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +9 -4
- package/lib/mock/mock-symbols.js +3 -1
- package/lib/mock/mock-utils.js +29 -5
- package/lib/util/cache.js +360 -0
- package/lib/web/cache/cache.js +24 -21
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +29 -14
- package/lib/web/cookies/parse.js +8 -3
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +43 -41
- package/lib/web/fetch/constants.js +12 -5
- package/lib/web/fetch/data-url.js +3 -3
- package/lib/web/fetch/formdata-parser.js +72 -45
- package/lib/web/fetch/formdata.js +65 -54
- package/lib/web/fetch/headers.js +118 -86
- package/lib/web/fetch/index.js +58 -67
- package/lib/web/fetch/request.js +136 -77
- package/lib/web/fetch/response.js +87 -56
- package/lib/web/fetch/util.js +259 -109
- package/lib/web/fetch/webidl.js +113 -68
- package/lib/web/websocket/connection.js +76 -147
- package/lib/web/websocket/constants.js +70 -10
- package/lib/web/websocket/events.js +4 -2
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +29 -33
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +128 -77
- package/lib/web/websocket/websocket.js +234 -135
- package/package.json +24 -36
- package/scripts/strip-comments.js +3 -1
- package/types/agent.d.ts +7 -7
- package/types/api.d.ts +24 -24
- package/types/balanced-pool.d.ts +11 -11
- package/types/cache-interceptor.d.ts +172 -0
- package/types/client.d.ts +11 -12
- package/types/cookies.d.ts +2 -0
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +113 -90
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/fetch.d.ts +17 -16
- package/types/formdata.d.ts +7 -7
- package/types/global-dispatcher.d.ts +4 -4
- package/types/global-origin.d.ts +5 -5
- package/types/handlers.d.ts +7 -7
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +44 -46
- package/types/interceptors.d.ts +25 -8
- package/types/mock-agent.d.ts +21 -18
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +19 -19
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +0 -4
- package/types/pool-stats.d.ts +8 -8
- package/types/pool.d.ts +12 -12
- package/types/proxy-agent.d.ts +4 -4
- package/types/readable.d.ts +18 -15
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +10 -10
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +44 -6
- package/types/websocket.d.ts +34 -1
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fetch/symbols.js +0 -9
- package/lib/web/fileapi/encoding.js +0 -290
- package/lib/web/fileapi/filereader.js +0 -344
- package/lib/web/fileapi/progressevent.js +0 -78
- package/lib/web/fileapi/symbols.js +0 -10
- package/lib/web/fileapi/util.js +0 -391
- package/lib/web/websocket/symbols.js +0 -12
- package/types/file.d.ts +0 -39
- package/types/filereader.d.ts +0 -54
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
safeHTTPMethods
|
|
5
|
+
} = require('../core/util')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
|
|
10
|
+
*/
|
|
11
|
+
function makeCacheKey (opts) {
|
|
12
|
+
if (!opts.origin) {
|
|
13
|
+
throw new Error('opts.origin is undefined')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @type {Record<string, string[] | string>} */
|
|
17
|
+
let headers
|
|
18
|
+
if (opts.headers == null) {
|
|
19
|
+
headers = {}
|
|
20
|
+
} else if (typeof opts.headers[Symbol.iterator] === 'function') {
|
|
21
|
+
headers = {}
|
|
22
|
+
for (const x of opts.headers) {
|
|
23
|
+
if (!Array.isArray(x)) {
|
|
24
|
+
throw new Error('opts.headers is not a valid header map')
|
|
25
|
+
}
|
|
26
|
+
const [key, val] = x
|
|
27
|
+
if (typeof key !== 'string' || typeof val !== 'string') {
|
|
28
|
+
throw new Error('opts.headers is not a valid header map')
|
|
29
|
+
}
|
|
30
|
+
headers[key] = val
|
|
31
|
+
}
|
|
32
|
+
} else if (typeof opts.headers === 'object') {
|
|
33
|
+
headers = opts.headers
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error('opts.headers is not an object')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
origin: opts.origin.toString(),
|
|
40
|
+
method: opts.method,
|
|
41
|
+
path: opts.path,
|
|
42
|
+
headers
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {any} key
|
|
48
|
+
*/
|
|
49
|
+
function assertCacheKey (key) {
|
|
50
|
+
if (typeof key !== 'object') {
|
|
51
|
+
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const property of ['origin', 'method', 'path']) {
|
|
55
|
+
if (typeof key[property] !== 'string') {
|
|
56
|
+
throw new TypeError(`expected key.${property} to be string, got ${typeof key[property]}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (key.headers !== undefined && typeof key.headers !== 'object') {
|
|
61
|
+
throw new TypeError(`expected headers to be object, got ${typeof key}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {any} value
|
|
67
|
+
*/
|
|
68
|
+
function assertCacheValue (value) {
|
|
69
|
+
if (typeof value !== 'object') {
|
|
70
|
+
throw new TypeError(`expected value to be object, got ${typeof value}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const property of ['statusCode', 'cachedAt', 'staleAt', 'deleteAt']) {
|
|
74
|
+
if (typeof value[property] !== 'number') {
|
|
75
|
+
throw new TypeError(`expected value.${property} to be number, got ${typeof value[property]}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof value.statusMessage !== 'string') {
|
|
80
|
+
throw new TypeError(`expected value.statusMessage to be string, got ${typeof value.statusMessage}`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (value.headers != null && typeof value.headers !== 'object') {
|
|
84
|
+
throw new TypeError(`expected value.rawHeaders to be object, got ${typeof value.headers}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (value.vary !== undefined && typeof value.vary !== 'object') {
|
|
88
|
+
throw new TypeError(`expected value.vary to be object, got ${typeof value.vary}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (value.etag !== undefined && typeof value.etag !== 'string') {
|
|
92
|
+
throw new TypeError(`expected value.etag to be string, got ${typeof value.etag}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control
|
|
98
|
+
* @see https://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
|
|
99
|
+
|
|
100
|
+
* @param {string | string[]} header
|
|
101
|
+
* @returns {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
|
|
102
|
+
*/
|
|
103
|
+
function parseCacheControlHeader (header) {
|
|
104
|
+
/**
|
|
105
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
|
|
106
|
+
*/
|
|
107
|
+
const output = {}
|
|
108
|
+
|
|
109
|
+
let directives
|
|
110
|
+
if (Array.isArray(header)) {
|
|
111
|
+
directives = []
|
|
112
|
+
|
|
113
|
+
for (const directive of header) {
|
|
114
|
+
directives.push(...directive.split(','))
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
directives = header.split(',')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < directives.length; i++) {
|
|
121
|
+
const directive = directives[i].toLowerCase()
|
|
122
|
+
const keyValueDelimiter = directive.indexOf('=')
|
|
123
|
+
|
|
124
|
+
let key
|
|
125
|
+
let value
|
|
126
|
+
if (keyValueDelimiter !== -1) {
|
|
127
|
+
key = directive.substring(0, keyValueDelimiter).trimStart()
|
|
128
|
+
value = directive.substring(keyValueDelimiter + 1)
|
|
129
|
+
} else {
|
|
130
|
+
key = directive.trim()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
switch (key) {
|
|
134
|
+
case 'min-fresh':
|
|
135
|
+
case 'max-stale':
|
|
136
|
+
case 'max-age':
|
|
137
|
+
case 's-maxage':
|
|
138
|
+
case 'stale-while-revalidate':
|
|
139
|
+
case 'stale-if-error': {
|
|
140
|
+
if (value === undefined || value[0] === ' ') {
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
value.length >= 2 &&
|
|
146
|
+
value[0] === '"' &&
|
|
147
|
+
value[value.length - 1] === '"'
|
|
148
|
+
) {
|
|
149
|
+
value = value.substring(1, value.length - 1)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const parsedValue = parseInt(value, 10)
|
|
153
|
+
// eslint-disable-next-line no-self-compare
|
|
154
|
+
if (parsedValue !== parsedValue) {
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (key === 'max-age' && key in output && output[key] >= parsedValue) {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
output[key] = parsedValue
|
|
163
|
+
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
case 'private':
|
|
167
|
+
case 'no-cache': {
|
|
168
|
+
if (value) {
|
|
169
|
+
// The private and no-cache directives can be unqualified (aka just
|
|
170
|
+
// `private` or `no-cache`) or qualified (w/ a value). When they're
|
|
171
|
+
// qualified, it's a list of headers like `no-cache=header1`,
|
|
172
|
+
// `no-cache="header1"`, or `no-cache="header1, header2"`
|
|
173
|
+
// If we're given multiple headers, the comma messes us up since
|
|
174
|
+
// we split the full header by commas. So, let's loop through the
|
|
175
|
+
// remaining parts in front of us until we find one that ends in a
|
|
176
|
+
// quote. We can then just splice all of the parts in between the
|
|
177
|
+
// starting quote and the ending quote out of the directives array
|
|
178
|
+
// and continue parsing like normal.
|
|
179
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
|
|
180
|
+
if (value[0] === '"') {
|
|
181
|
+
// Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
|
|
182
|
+
|
|
183
|
+
// Add the first header on and cut off the leading quote
|
|
184
|
+
const headers = [value.substring(1)]
|
|
185
|
+
|
|
186
|
+
let foundEndingQuote = value[value.length - 1] === '"'
|
|
187
|
+
if (!foundEndingQuote) {
|
|
188
|
+
// Something like `no-cache="some-header, another-header"`
|
|
189
|
+
// This can still be something invalid, e.g. `no-cache="some-header, ...`
|
|
190
|
+
for (let j = i + 1; j < directives.length; j++) {
|
|
191
|
+
const nextPart = directives[j]
|
|
192
|
+
const nextPartLength = nextPart.length
|
|
193
|
+
|
|
194
|
+
headers.push(nextPart.trim())
|
|
195
|
+
|
|
196
|
+
if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
|
|
197
|
+
foundEndingQuote = true
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (foundEndingQuote) {
|
|
204
|
+
let lastHeader = headers[headers.length - 1]
|
|
205
|
+
if (lastHeader[lastHeader.length - 1] === '"') {
|
|
206
|
+
lastHeader = lastHeader.substring(0, lastHeader.length - 1)
|
|
207
|
+
headers[headers.length - 1] = lastHeader
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (key in output) {
|
|
211
|
+
output[key] = output[key].concat(headers)
|
|
212
|
+
} else {
|
|
213
|
+
output[key] = headers
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// Something like `no-cache=some-header`
|
|
218
|
+
if (key in output) {
|
|
219
|
+
output[key] = output[key].concat(value)
|
|
220
|
+
} else {
|
|
221
|
+
output[key] = [value]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
break
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// eslint-disable-next-line no-fallthrough
|
|
229
|
+
case 'public':
|
|
230
|
+
case 'no-store':
|
|
231
|
+
case 'must-revalidate':
|
|
232
|
+
case 'proxy-revalidate':
|
|
233
|
+
case 'immutable':
|
|
234
|
+
case 'no-transform':
|
|
235
|
+
case 'must-understand':
|
|
236
|
+
case 'only-if-cached':
|
|
237
|
+
if (value) {
|
|
238
|
+
// These are qualified (something like `public=...`) when they aren't
|
|
239
|
+
// allowed to be, skip
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
output[key] = true
|
|
244
|
+
break
|
|
245
|
+
default:
|
|
246
|
+
// Ignore unknown directives as per https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.3-1
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return output
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {string | string[]} varyHeader Vary header from the server
|
|
256
|
+
* @param {Record<string, string | string[]>} headers Request headers
|
|
257
|
+
* @returns {Record<string, string | string[]>}
|
|
258
|
+
*/
|
|
259
|
+
function parseVaryHeader (varyHeader, headers) {
|
|
260
|
+
if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
|
|
261
|
+
return headers
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const output = /** @type {Record<string, string | string[]>} */ ({})
|
|
265
|
+
|
|
266
|
+
const varyingHeaders = typeof varyHeader === 'string'
|
|
267
|
+
? varyHeader.split(',')
|
|
268
|
+
: varyHeader
|
|
269
|
+
for (const header of varyingHeaders) {
|
|
270
|
+
const trimmedHeader = header.trim().toLowerCase()
|
|
271
|
+
|
|
272
|
+
if (headers[trimmedHeader]) {
|
|
273
|
+
output[trimmedHeader] = headers[trimmedHeader]
|
|
274
|
+
} else {
|
|
275
|
+
return undefined
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return output
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
|
|
284
|
+
* however, including them in cached resposnes serves little to no purpose.
|
|
285
|
+
*
|
|
286
|
+
* @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
|
|
287
|
+
*
|
|
288
|
+
* @param {string} etag
|
|
289
|
+
* @returns {boolean}
|
|
290
|
+
*/
|
|
291
|
+
function isEtagUsable (etag) {
|
|
292
|
+
if (etag.length <= 2) {
|
|
293
|
+
// Shortest an etag can be is two chars (just ""). This is where we deviate
|
|
294
|
+
// from the spec requiring a min of 3 chars however
|
|
295
|
+
return false
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (etag[0] === '"' && etag[etag.length - 1] === '"') {
|
|
299
|
+
// ETag: ""asd123"" or ETag: "W/"asd123"", kinda undefined behavior in the
|
|
300
|
+
// spec. Some servers will accept these while others don't.
|
|
301
|
+
// ETag: "asd123"
|
|
302
|
+
return !(etag[1] === '"' || etag.startsWith('"W/'))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (etag.startsWith('W/"') && etag[etag.length - 1] === '"') {
|
|
306
|
+
// ETag: W/"", also where we deviate from the spec & require a min of 3
|
|
307
|
+
// chars
|
|
308
|
+
// ETag: for W/"", W/"asd123"
|
|
309
|
+
return etag.length !== 4
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Anything else
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {unknown} store
|
|
318
|
+
* @returns {asserts store is import('../../types/cache-interceptor.d.ts').default.CacheStore}
|
|
319
|
+
*/
|
|
320
|
+
function assertCacheStore (store, name = 'CacheStore') {
|
|
321
|
+
if (typeof store !== 'object' || store === null) {
|
|
322
|
+
throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store === null ? 'null' : typeof store}`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
for (const fn of ['get', 'createWriteStream', 'delete']) {
|
|
326
|
+
if (typeof store[fn] !== 'function') {
|
|
327
|
+
throw new TypeError(`${name} needs to have a \`${fn}()\` function`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* @param {unknown} methods
|
|
333
|
+
* @returns {asserts methods is import('../../types/cache-interceptor.d.ts').default.CacheMethods[]}
|
|
334
|
+
*/
|
|
335
|
+
function assertCacheMethods (methods, name = 'CacheMethods') {
|
|
336
|
+
if (!Array.isArray(methods)) {
|
|
337
|
+
throw new TypeError(`expected type of ${name} needs to be an array, got ${methods === null ? 'null' : typeof methods}`)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (methods.length === 0) {
|
|
341
|
+
throw new TypeError(`${name} needs to have at least one method`)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (const method of methods) {
|
|
345
|
+
if (!safeHTTPMethods.includes(method)) {
|
|
346
|
+
throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
module.exports = {
|
|
352
|
+
makeCacheKey,
|
|
353
|
+
assertCacheKey,
|
|
354
|
+
assertCacheValue,
|
|
355
|
+
parseCacheControlHeader,
|
|
356
|
+
parseVaryHeader,
|
|
357
|
+
isEtagUsable,
|
|
358
|
+
assertCacheMethods,
|
|
359
|
+
assertCacheStore
|
|
360
|
+
}
|
package/lib/web/cache/cache.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kConstruct } = require('
|
|
3
|
+
const { kConstruct } = require('../../core/symbols')
|
|
4
4
|
const { urlEquals, getFieldValues } = require('./util')
|
|
5
5
|
const { kEnumerableProperty, isDisturbed } = require('../../core/util')
|
|
6
6
|
const { webidl } = require('../fetch/webidl')
|
|
7
|
-
const {
|
|
8
|
-
const { Request, fromInnerRequest } = require('../fetch/request')
|
|
9
|
-
const { kState } = require('../fetch/symbols')
|
|
7
|
+
const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
|
|
8
|
+
const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
|
|
10
9
|
const { fetching } = require('../fetch/index')
|
|
11
10
|
const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
|
|
12
11
|
const assert = require('node:assert')
|
|
@@ -116,7 +115,7 @@ class Cache {
|
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
// 3.1
|
|
119
|
-
const r = request
|
|
118
|
+
const r = getRequestState(request)
|
|
120
119
|
|
|
121
120
|
// 3.2
|
|
122
121
|
if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') {
|
|
@@ -134,7 +133,7 @@ class Cache {
|
|
|
134
133
|
// 5.
|
|
135
134
|
for (const request of requests) {
|
|
136
135
|
// 5.1
|
|
137
|
-
const r = new Request(request)
|
|
136
|
+
const r = getRequestState(new Request(request))
|
|
138
137
|
|
|
139
138
|
// 5.2
|
|
140
139
|
if (!urlIsHttpHttpsScheme(r.url)) {
|
|
@@ -270,10 +269,10 @@ class Cache {
|
|
|
270
269
|
let innerRequest = null
|
|
271
270
|
|
|
272
271
|
// 2.
|
|
273
|
-
if (request
|
|
274
|
-
innerRequest = request
|
|
272
|
+
if (webidl.is.Request(request)) {
|
|
273
|
+
innerRequest = getRequestState(request)
|
|
275
274
|
} else { // 3.
|
|
276
|
-
innerRequest = new Request(request)
|
|
275
|
+
innerRequest = getRequestState(new Request(request))
|
|
277
276
|
}
|
|
278
277
|
|
|
279
278
|
// 4.
|
|
@@ -285,7 +284,7 @@ class Cache {
|
|
|
285
284
|
}
|
|
286
285
|
|
|
287
286
|
// 5.
|
|
288
|
-
const innerResponse = response
|
|
287
|
+
const innerResponse = getResponseState(response)
|
|
289
288
|
|
|
290
289
|
// 6.
|
|
291
290
|
if (innerResponse.status === 206) {
|
|
@@ -335,7 +334,7 @@ class Cache {
|
|
|
335
334
|
const reader = stream.getReader()
|
|
336
335
|
|
|
337
336
|
// 11.3
|
|
338
|
-
readAllBytes(reader
|
|
337
|
+
readAllBytes(reader, bodyReadPromise.resolve, bodyReadPromise.reject)
|
|
339
338
|
} else {
|
|
340
339
|
bodyReadPromise.resolve(undefined)
|
|
341
340
|
}
|
|
@@ -402,8 +401,8 @@ class Cache {
|
|
|
402
401
|
*/
|
|
403
402
|
let r = null
|
|
404
403
|
|
|
405
|
-
if (request
|
|
406
|
-
r = request
|
|
404
|
+
if (webidl.is.Request(request)) {
|
|
405
|
+
r = getRequestState(request)
|
|
407
406
|
|
|
408
407
|
if (r.method !== 'GET' && !options.ignoreMethod) {
|
|
409
408
|
return false
|
|
@@ -411,7 +410,7 @@ class Cache {
|
|
|
411
410
|
} else {
|
|
412
411
|
assert(typeof request === 'string')
|
|
413
412
|
|
|
414
|
-
r = new Request(request)
|
|
413
|
+
r = getRequestState(new Request(request))
|
|
415
414
|
}
|
|
416
415
|
|
|
417
416
|
/** @type {CacheBatchOperation[]} */
|
|
@@ -468,16 +467,16 @@ class Cache {
|
|
|
468
467
|
// 2.
|
|
469
468
|
if (request !== undefined) {
|
|
470
469
|
// 2.1
|
|
471
|
-
if (request
|
|
470
|
+
if (webidl.is.Request(request)) {
|
|
472
471
|
// 2.1.1
|
|
473
|
-
r = request
|
|
472
|
+
r = getRequestState(request)
|
|
474
473
|
|
|
475
474
|
// 2.1.2
|
|
476
475
|
if (r.method !== 'GET' && !options.ignoreMethod) {
|
|
477
476
|
return []
|
|
478
477
|
}
|
|
479
478
|
} else if (typeof request === 'string') { // 2.2
|
|
480
|
-
r = new Request(request)
|
|
479
|
+
r = getRequestState(new Request(request))
|
|
481
480
|
}
|
|
482
481
|
}
|
|
483
482
|
|
|
@@ -515,6 +514,7 @@ class Cache {
|
|
|
515
514
|
for (const request of requests) {
|
|
516
515
|
const requestObject = fromInnerRequest(
|
|
517
516
|
request,
|
|
517
|
+
undefined,
|
|
518
518
|
new AbortController().signal,
|
|
519
519
|
'immutable'
|
|
520
520
|
)
|
|
@@ -749,9 +749,9 @@ class Cache {
|
|
|
749
749
|
|
|
750
750
|
// 2.
|
|
751
751
|
if (request !== undefined) {
|
|
752
|
-
if (request
|
|
752
|
+
if (webidl.is.Request(request)) {
|
|
753
753
|
// 2.1.1
|
|
754
|
-
r = request
|
|
754
|
+
r = getRequestState(request)
|
|
755
755
|
|
|
756
756
|
// 2.1.2
|
|
757
757
|
if (r.method !== 'GET' && !options.ignoreMethod) {
|
|
@@ -759,7 +759,7 @@ class Cache {
|
|
|
759
759
|
}
|
|
760
760
|
} else if (typeof request === 'string') {
|
|
761
761
|
// 2.2.1
|
|
762
|
-
r = new Request(request)
|
|
762
|
+
r = getRequestState(new Request(request))
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
|
|
@@ -848,7 +848,10 @@ webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([
|
|
|
848
848
|
}
|
|
849
849
|
])
|
|
850
850
|
|
|
851
|
-
webidl.converters.Response = webidl.interfaceConverter(
|
|
851
|
+
webidl.converters.Response = webidl.interfaceConverter(
|
|
852
|
+
webidl.is.Response,
|
|
853
|
+
'Response'
|
|
854
|
+
)
|
|
852
855
|
|
|
853
856
|
webidl.converters['sequence<RequestInfo>'] = webidl.sequenceConverter(
|
|
854
857
|
webidl.converters.RequestInfo
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kConstruct } = require('./symbols')
|
|
4
3
|
const { Cache } = require('./cache')
|
|
5
4
|
const { webidl } = require('../fetch/webidl')
|
|
6
5
|
const { kEnumerableProperty } = require('../../core/util')
|
|
6
|
+
const { kConstruct } = require('../../core/symbols')
|
|
7
7
|
|
|
8
8
|
class CacheStorage {
|
|
9
9
|
/**
|
package/lib/web/cookies/index.js
CHANGED
|
@@ -5,18 +5,20 @@ const { stringify } = require('./util')
|
|
|
5
5
|
const { webidl } = require('../fetch/webidl')
|
|
6
6
|
const { Headers } = require('../fetch/headers')
|
|
7
7
|
|
|
8
|
+
const brandChecks = webidl.brandCheckMultiple([Headers, globalThis.Headers].filter(Boolean))
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @typedef {Object} Cookie
|
|
10
12
|
* @property {string} name
|
|
11
13
|
* @property {string} value
|
|
12
|
-
* @property {Date|number
|
|
13
|
-
* @property {number
|
|
14
|
-
* @property {string
|
|
15
|
-
* @property {string
|
|
16
|
-
* @property {boolean
|
|
17
|
-
* @property {boolean
|
|
18
|
-
* @property {'Strict'|'Lax'|'None'} sameSite
|
|
19
|
-
* @property {string[]} unparsed
|
|
14
|
+
* @property {Date|number} [expires]
|
|
15
|
+
* @property {number} [maxAge]
|
|
16
|
+
* @property {string} [domain]
|
|
17
|
+
* @property {string} [path]
|
|
18
|
+
* @property {boolean} [secure]
|
|
19
|
+
* @property {boolean} [httpOnly]
|
|
20
|
+
* @property {'Strict'|'Lax'|'None'} [sameSite]
|
|
21
|
+
* @property {string[]} [unparsed]
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -26,9 +28,11 @@ const { Headers } = require('../fetch/headers')
|
|
|
26
28
|
function getCookies (headers) {
|
|
27
29
|
webidl.argumentLengthCheck(arguments, 1, 'getCookies')
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
brandChecks(headers)
|
|
30
32
|
|
|
31
33
|
const cookie = headers.get('cookie')
|
|
34
|
+
|
|
35
|
+
/** @type {Record<string, string>} */
|
|
32
36
|
const out = {}
|
|
33
37
|
|
|
34
38
|
if (!cookie) {
|
|
@@ -51,7 +55,7 @@ function getCookies (headers) {
|
|
|
51
55
|
* @returns {void}
|
|
52
56
|
*/
|
|
53
57
|
function deleteCookie (headers, name, attributes) {
|
|
54
|
-
|
|
58
|
+
brandChecks(headers)
|
|
55
59
|
|
|
56
60
|
const prefix = 'deleteCookie'
|
|
57
61
|
webidl.argumentLengthCheck(arguments, 2, prefix)
|
|
@@ -76,7 +80,7 @@ function deleteCookie (headers, name, attributes) {
|
|
|
76
80
|
function getSetCookies (headers) {
|
|
77
81
|
webidl.argumentLengthCheck(arguments, 1, 'getSetCookies')
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
brandChecks(headers)
|
|
80
84
|
|
|
81
85
|
const cookies = headers.getSetCookie()
|
|
82
86
|
|
|
@@ -87,6 +91,16 @@ function getSetCookies (headers) {
|
|
|
87
91
|
return cookies.map((pair) => parseSetCookie(pair))
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Parses a cookie string
|
|
96
|
+
* @param {string} cookie
|
|
97
|
+
*/
|
|
98
|
+
function parseCookie (cookie) {
|
|
99
|
+
cookie = webidl.converters.DOMString(cookie)
|
|
100
|
+
|
|
101
|
+
return parseSetCookie(cookie)
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/**
|
|
91
105
|
* @param {Headers} headers
|
|
92
106
|
* @param {Cookie} cookie
|
|
@@ -95,14 +109,14 @@ function getSetCookies (headers) {
|
|
|
95
109
|
function setCookie (headers, cookie) {
|
|
96
110
|
webidl.argumentLengthCheck(arguments, 2, 'setCookie')
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
brandChecks(headers)
|
|
99
113
|
|
|
100
114
|
cookie = webidl.converters.Cookie(cookie)
|
|
101
115
|
|
|
102
116
|
const str = stringify(cookie)
|
|
103
117
|
|
|
104
118
|
if (str) {
|
|
105
|
-
headers.append('
|
|
119
|
+
headers.append('set-cookie', str, true)
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
|
|
@@ -180,5 +194,6 @@ module.exports = {
|
|
|
180
194
|
getCookies,
|
|
181
195
|
deleteCookie,
|
|
182
196
|
getSetCookies,
|
|
183
|
-
setCookie
|
|
197
|
+
setCookie,
|
|
198
|
+
parseCookie
|
|
184
199
|
}
|
package/lib/web/cookies/parse.js
CHANGED
|
@@ -4,12 +4,13 @@ const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
|
|
|
4
4
|
const { isCTLExcludingHtab } = require('./util')
|
|
5
5
|
const { collectASequenceOfCodePointsFast } = require('../fetch/data-url')
|
|
6
6
|
const assert = require('node:assert')
|
|
7
|
+
const { unescape } = require('node:querystring')
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @description Parses the field-value attributes of a set-cookie header string.
|
|
10
11
|
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
|
|
11
12
|
* @param {string} header
|
|
12
|
-
* @returns if the header is invalid, null will be returned
|
|
13
|
+
* @returns {import('./index').Cookie|null} if the header is invalid, null will be returned
|
|
13
14
|
*/
|
|
14
15
|
function parseSetCookie (header) {
|
|
15
16
|
// 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F
|
|
@@ -76,8 +77,12 @@ function parseSetCookie (header) {
|
|
|
76
77
|
|
|
77
78
|
// 6. The cookie-name is the name string, and the cookie-value is the
|
|
78
79
|
// value string.
|
|
80
|
+
// https://datatracker.ietf.org/doc/html/rfc6265
|
|
81
|
+
// To maximize compatibility with user agents, servers that wish to
|
|
82
|
+
// store arbitrary data in a cookie-value SHOULD encode that data, for
|
|
83
|
+
// example, using Base64 [RFC4648].
|
|
79
84
|
return {
|
|
80
|
-
name, value, ...parseUnparsedAttributes(unparsedAttributes)
|
|
85
|
+
name, value: unescape(value), ...parseUnparsedAttributes(unparsedAttributes)
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -85,7 +90,7 @@ function parseSetCookie (header) {
|
|
|
85
90
|
* Parses the remaining attributes of a set-cookie header
|
|
86
91
|
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
|
|
87
92
|
* @param {string} unparsedAttributes
|
|
88
|
-
* @param {
|
|
93
|
+
* @param {Object.<string, unknown>} [cookieAttributeList={}]
|
|
89
94
|
*/
|
|
90
95
|
function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) {
|
|
91
96
|
// 1. If the unparsed-attributes string is empty, skip the rest of
|