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.
Files changed (156) hide show
  1. package/README.md +27 -46
  2. package/docs/docs/api/Agent.md +14 -17
  3. package/docs/docs/api/BalancedPool.md +16 -16
  4. package/docs/docs/api/CacheStore.md +131 -0
  5. package/docs/docs/api/Client.md +12 -14
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -194
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
  9. package/docs/docs/api/MockAgent.md +5 -3
  10. package/docs/docs/api/MockClient.md +5 -5
  11. package/docs/docs/api/MockPool.md +4 -3
  12. package/docs/docs/api/Pool.md +15 -16
  13. package/docs/docs/api/PoolStats.md +1 -1
  14. package/docs/docs/api/ProxyAgent.md +3 -3
  15. package/docs/docs/api/RedirectHandler.md +1 -1
  16. package/docs/docs/api/RetryAgent.md +1 -1
  17. package/docs/docs/api/RetryHandler.md +4 -4
  18. package/docs/docs/api/WebSocket.md +46 -4
  19. package/docs/docs/api/api-lifecycle.md +11 -11
  20. package/docs/docs/best-practices/mocking-request.md +2 -2
  21. package/docs/docs/best-practices/proxy.md +1 -1
  22. package/index.d.ts +1 -1
  23. package/index.js +23 -7
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-connect.js +3 -1
  26. package/lib/api/api-pipeline.js +7 -6
  27. package/lib/api/api-request.js +33 -48
  28. package/lib/api/api-stream.js +39 -50
  29. package/lib/api/api-upgrade.js +5 -3
  30. package/lib/api/readable.js +235 -62
  31. package/lib/api/util.js +2 -0
  32. package/lib/cache/memory-cache-store.js +177 -0
  33. package/lib/cache/sqlite-cache-store.js +446 -0
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +6 -6
  37. package/lib/core/request.js +13 -11
  38. package/lib/core/symbols.js +2 -1
  39. package/lib/core/tree.js +9 -1
  40. package/lib/core/util.js +237 -49
  41. package/lib/dispatcher/agent.js +3 -17
  42. package/lib/dispatcher/balanced-pool.js +5 -8
  43. package/lib/dispatcher/client-h1.js +379 -134
  44. package/lib/dispatcher/client-h2.js +173 -107
  45. package/lib/dispatcher/client.js +19 -32
  46. package/lib/dispatcher/dispatcher-base.js +6 -35
  47. package/lib/dispatcher/dispatcher.js +7 -24
  48. package/lib/dispatcher/fixed-queue.js +91 -49
  49. package/lib/dispatcher/pool-stats.js +2 -0
  50. package/lib/dispatcher/pool.js +3 -6
  51. package/lib/dispatcher/proxy-agent.js +3 -6
  52. package/lib/handler/cache-handler.js +393 -0
  53. package/lib/handler/cache-revalidation-handler.js +124 -0
  54. package/lib/handler/decorator-handler.js +27 -0
  55. package/lib/handler/redirect-handler.js +54 -59
  56. package/lib/handler/retry-handler.js +77 -109
  57. package/lib/handler/unwrap-handler.js +96 -0
  58. package/lib/handler/wrap-handler.js +98 -0
  59. package/lib/interceptor/cache.js +350 -0
  60. package/lib/interceptor/dns.js +375 -0
  61. package/lib/interceptor/dump.js +2 -2
  62. package/lib/interceptor/redirect.js +11 -14
  63. package/lib/interceptor/response-error.js +18 -7
  64. package/lib/llhttp/constants.d.ts +97 -0
  65. package/lib/llhttp/constants.js +412 -192
  66. package/lib/llhttp/constants.js.map +1 -0
  67. package/lib/llhttp/llhttp-wasm.js +11 -1
  68. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  69. package/lib/llhttp/utils.d.ts +2 -0
  70. package/lib/llhttp/utils.js +9 -9
  71. package/lib/llhttp/utils.js.map +1 -0
  72. package/lib/mock/mock-agent.js +5 -8
  73. package/lib/mock/mock-client.js +9 -4
  74. package/lib/mock/mock-errors.js +3 -1
  75. package/lib/mock/mock-interceptor.js +8 -6
  76. package/lib/mock/mock-pool.js +9 -4
  77. package/lib/mock/mock-symbols.js +3 -1
  78. package/lib/mock/mock-utils.js +29 -5
  79. package/lib/util/cache.js +360 -0
  80. package/lib/web/cache/cache.js +24 -21
  81. package/lib/web/cache/cachestorage.js +1 -1
  82. package/lib/web/cookies/index.js +29 -14
  83. package/lib/web/cookies/parse.js +8 -3
  84. package/lib/web/eventsource/eventsource-stream.js +9 -8
  85. package/lib/web/eventsource/eventsource.js +10 -6
  86. package/lib/web/fetch/body.js +43 -41
  87. package/lib/web/fetch/constants.js +12 -5
  88. package/lib/web/fetch/data-url.js +3 -3
  89. package/lib/web/fetch/formdata-parser.js +72 -45
  90. package/lib/web/fetch/formdata.js +65 -54
  91. package/lib/web/fetch/headers.js +118 -86
  92. package/lib/web/fetch/index.js +58 -67
  93. package/lib/web/fetch/request.js +136 -77
  94. package/lib/web/fetch/response.js +87 -56
  95. package/lib/web/fetch/util.js +259 -109
  96. package/lib/web/fetch/webidl.js +113 -68
  97. package/lib/web/websocket/connection.js +76 -147
  98. package/lib/web/websocket/constants.js +70 -10
  99. package/lib/web/websocket/events.js +4 -2
  100. package/lib/web/websocket/frame.js +45 -3
  101. package/lib/web/websocket/receiver.js +29 -33
  102. package/lib/web/websocket/sender.js +18 -13
  103. package/lib/web/websocket/stream/websocketerror.js +83 -0
  104. package/lib/web/websocket/stream/websocketstream.js +485 -0
  105. package/lib/web/websocket/util.js +128 -77
  106. package/lib/web/websocket/websocket.js +234 -135
  107. package/package.json +24 -36
  108. package/scripts/strip-comments.js +3 -1
  109. package/types/agent.d.ts +7 -7
  110. package/types/api.d.ts +24 -24
  111. package/types/balanced-pool.d.ts +11 -11
  112. package/types/cache-interceptor.d.ts +172 -0
  113. package/types/client.d.ts +11 -12
  114. package/types/cookies.d.ts +2 -0
  115. package/types/diagnostics-channel.d.ts +10 -10
  116. package/types/dispatcher.d.ts +113 -90
  117. package/types/env-http-proxy-agent.d.ts +2 -2
  118. package/types/errors.d.ts +53 -47
  119. package/types/fetch.d.ts +17 -16
  120. package/types/formdata.d.ts +7 -7
  121. package/types/global-dispatcher.d.ts +4 -4
  122. package/types/global-origin.d.ts +5 -5
  123. package/types/handlers.d.ts +7 -7
  124. package/types/header.d.ts +157 -1
  125. package/types/index.d.ts +44 -46
  126. package/types/interceptors.d.ts +25 -8
  127. package/types/mock-agent.d.ts +21 -18
  128. package/types/mock-client.d.ts +4 -4
  129. package/types/mock-errors.d.ts +3 -3
  130. package/types/mock-interceptor.d.ts +19 -19
  131. package/types/mock-pool.d.ts +4 -4
  132. package/types/patch.d.ts +0 -4
  133. package/types/pool-stats.d.ts +8 -8
  134. package/types/pool.d.ts +12 -12
  135. package/types/proxy-agent.d.ts +4 -4
  136. package/types/readable.d.ts +18 -15
  137. package/types/retry-agent.d.ts +1 -1
  138. package/types/retry-handler.d.ts +10 -10
  139. package/types/util.d.ts +3 -3
  140. package/types/utility.d.ts +7 -0
  141. package/types/webidl.d.ts +44 -6
  142. package/types/websocket.d.ts +34 -1
  143. package/docs/docs/api/DispatchInterceptor.md +0 -60
  144. package/lib/interceptor/redirect-interceptor.js +0 -21
  145. package/lib/mock/pluralizer.js +0 -29
  146. package/lib/web/cache/symbols.js +0 -5
  147. package/lib/web/fetch/file.js +0 -126
  148. package/lib/web/fetch/symbols.js +0 -9
  149. package/lib/web/fileapi/encoding.js +0 -290
  150. package/lib/web/fileapi/filereader.js +0 -344
  151. package/lib/web/fileapi/progressevent.js +0 -78
  152. package/lib/web/fileapi/symbols.js +0 -10
  153. package/lib/web/fileapi/util.js +0 -391
  154. package/lib/web/websocket/symbols.js +0 -12
  155. package/types/file.d.ts +0 -39
  156. 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
+ }
@@ -1,12 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const { kConstruct } = require('./symbols')
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 { Response, cloneResponse, fromInnerResponse } = require('../fetch/response')
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[kState]
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)[kState]
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 instanceof Request) {
274
- innerRequest = request[kState]
272
+ if (webidl.is.Request(request)) {
273
+ innerRequest = getRequestState(request)
275
274
  } else { // 3.
276
- innerRequest = new Request(request)[kState]
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[kState]
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).then(bodyReadPromise.resolve, bodyReadPromise.reject)
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 instanceof Request) {
406
- r = request[kState]
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)[kState]
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 instanceof Request) {
470
+ if (webidl.is.Request(request)) {
472
471
  // 2.1.1
473
- r = request[kState]
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)[kState]
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 instanceof Request) {
752
+ if (webidl.is.Request(request)) {
753
753
  // 2.1.1
754
- r = request[kState]
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)[kState]
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(Response)
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
  /**
@@ -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|undefined} expires
13
- * @property {number|undefined} maxAge
14
- * @property {string|undefined} domain
15
- * @property {string|undefined} path
16
- * @property {boolean|undefined} secure
17
- * @property {boolean|undefined} httpOnly
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
- webidl.brandCheck(headers, Headers, { strict: false })
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
- webidl.brandCheck(headers, Headers, { strict: false })
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
- webidl.brandCheck(headers, Headers, { strict: false })
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
- webidl.brandCheck(headers, Headers, { strict: false })
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('Set-Cookie', str)
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
  }
@@ -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 {[Object.<string, unknown>]={}} cookieAttributeList
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