@openclaw/discord 2026.6.11 → 2026.6.34

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 (152) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{api-4GFMoWu0.js → api-CjnGBhM3.js} +4 -1
  3. package/dist/api.js +14 -14
  4. package/dist/{approval-handler.runtime-SsFtZQA6.js → approval-handler.runtime-BWPQrzpP.js} +9 -4
  5. package/dist/{audit-CAnmyf2A.js → audit-BwJ_2OjL.js} +3 -3
  6. package/dist/{channel-YA7AbYP3.js → channel-DU4jnQgl.js} +15 -15
  7. package/dist/{channel-actions-BJ3n5Z1l.js → channel-actions-CwmIqnYN.js} +2 -2
  8. package/dist/{channel-actions.runtime-D2QTRgZ9.js → channel-actions.runtime-DvpTfaJ4.js} +5 -5
  9. package/dist/channel-plugin-api.js +1 -1
  10. package/dist/{channel.setup-B1zDEAR7.js → channel.setup-glZ5Y98a.js} +2 -2
  11. package/dist/{components-Dm4c_ce6.js → components-j6lTZBsT.js} +1 -1
  12. package/dist/{conversation-identity-DZ0BD7xO.js → conversation-identity-36FFBwm5.js} +2 -2
  13. package/dist/{directory-config-BK1OhHa3.js → directory-config-BYMFzL29.js} +1 -1
  14. package/dist/directory-contract-api.js +1 -1
  15. package/dist/{directory-live-DP3CmwGf.js → directory-live-9JtJCiu8.js} +2 -2
  16. package/dist/{handle-action.guild-admin-B2cOoU2D.js → handle-action.guild-admin-xxGYrp-I.js} +2 -2
  17. package/dist/index.js +1 -1
  18. package/dist/{manager.runtime-CPHxr9B3.js → manager.runtime-NxCd-PVt.js} +3 -3
  19. package/dist/{message-handler-CKaTgBIV.js → message-handler-cizxbaDc.js} +7 -6
  20. package/dist/{message-handler.preflight--eAiaewz.js → message-handler.preflight-CbD0u7U7.js} +10 -10
  21. package/dist/{message-handler.process-PhKAmrEd.js → message-handler.process-Do4EM591.js} +10 -10
  22. package/dist/{message-utils-O1vK3kPu.js → message-utils-CJeAoISH.js} +1 -1
  23. package/dist/{outbound-adapter-fO_7hbTh.js → outbound-adapter-Dxs6iKEq.js} +62 -131
  24. package/dist/{pluralkit-B3MzPaPU.js → pluralkit-DI6RYaVj.js} +1 -1
  25. package/dist/presence-cache-z1N8MbJ3.js +45 -0
  26. package/dist/{probe-CxnDikWm.js → probe-esE_Oqdm.js} +1 -1
  27. package/dist/{probe.runtime-DLFkSDVB.js → probe.runtime-C3Atgpvr.js} +1 -1
  28. package/dist/{provider-BJaJ20MY.js → provider-B-s93q9s.js} +76 -26
  29. package/dist/{provider-session.runtime-D6xc-60s.js → provider-session.runtime-BKMOqHpd.js} +3 -3
  30. package/dist/provider.runtime-CaTJrpnl.js +2 -0
  31. package/dist/{resolve-allowlist-common-BDpBb8dF.js → resolve-allowlist-common-3gB2Gpmm.js} +1 -1
  32. package/dist/{resolve-channels-iJIjYfRw.js → resolve-channels-B3gQf8QL.js} +3 -3
  33. package/dist/{resolve-users-_fv1mm1f.js → resolve-users-BO10y5fJ.js} +3 -3
  34. package/dist/{runtime-ywXByivr.js → runtime-CFlR3L9X.js} +8 -8
  35. package/dist/runtime-api.actions.js +2 -2
  36. package/dist/runtime-api.js +20 -20
  37. package/dist/runtime-api.lookup.js +5 -5
  38. package/dist/runtime-api.monitor-CRiBIQEI.js +6 -0
  39. package/dist/runtime-api.monitor.js +6 -5
  40. package/dist/runtime-api.send.js +5 -5
  41. package/dist/runtime-api.threads.js +3 -3
  42. package/dist/{send-Dc5uQX5H.js → send-JHxfT4Pk.js} +6 -4
  43. package/dist/{send.components-D4bl20xr.js → send.components-CN35E2Ck.js} +11 -6
  44. package/dist/{send.outbound-5axvwLkl.js → send.outbound-B-cA1Esv.js} +15 -8
  45. package/dist/{send.receipt-B21MspvT.js → send.receipt-CGecJDin.js} +10 -1
  46. package/dist/{send.shared-9082A2sO.js → send.shared-vcbfThdy.js} +107 -16
  47. package/dist/session-binding-contract-api.js +1 -1
  48. package/dist/setup-plugin-api.js +1 -1
  49. package/dist/{subagent-hooks-NTv6B4up.js → subagent-hooks-20wXhVzZ.js} +2 -2
  50. package/dist/subagent-hooks-api.js +1 -1
  51. package/dist/{system-events-BzaI1WpN.js → system-events-DR27TE7p.js} +1 -1
  52. package/dist/{target-resolver-agnOrtOU.js → target-resolver-CDi1gI90.js} +2 -2
  53. package/dist/targets-C6yd3y8R.js +3 -0
  54. package/dist/{thread-bindings-DNitaHVh.js → thread-bindings-DU4T6nDo.js} +4 -4
  55. package/dist/{thread-bindings.discord-api-CjYNuvZa.js → thread-bindings.discord-api-CYkr-3Gj.js} +4 -4
  56. package/dist/{thread-bindings.manager-PZvJ4TJx.js → thread-bindings.manager-BqWs1UE2.js} +3 -3
  57. package/dist/{transcripts-source-Bi8N6h5X.js → transcripts-source-D-lBI5ZD.js} +1 -1
  58. package/dist/transcripts-source-api.js +1 -1
  59. package/dist/{typing-BdRqXUN3.js → typing-xr05bL-r.js} +1 -1
  60. package/node_modules/undici/README.md +8 -2
  61. package/node_modules/undici/docs/docs/api/Agent.md +248 -44
  62. package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
  63. package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
  64. package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
  65. package/node_modules/undici/docs/docs/api/Client.md +369 -179
  66. package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
  67. package/node_modules/undici/docs/docs/api/Connector.md +118 -36
  68. package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
  69. package/node_modules/undici/docs/docs/api/Cookies.md +122 -71
  70. package/node_modules/undici/docs/docs/api/Debug.md +34 -18
  71. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
  72. package/node_modules/undici/docs/docs/api/Dispatcher.md +485 -1102
  73. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +101 -87
  74. package/node_modules/undici/docs/docs/api/Errors.md +503 -40
  75. package/node_modules/undici/docs/docs/api/EventSource.md +294 -32
  76. package/node_modules/undici/docs/docs/api/Fetch.md +680 -31
  77. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -98
  78. package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
  79. package/node_modules/undici/docs/docs/api/Interceptors.md +414 -0
  80. package/node_modules/undici/docs/docs/api/MockAgent.md +333 -314
  81. package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
  82. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
  83. package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
  84. package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
  85. package/node_modules/undici/docs/docs/api/MockPool.md +321 -258
  86. package/node_modules/undici/docs/docs/api/Pool.md +235 -43
  87. package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
  88. package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
  89. package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -63
  90. package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -26
  91. package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -77
  92. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
  93. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +264 -542
  94. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +162 -187
  95. package/node_modules/undici/docs/docs/api/Util.md +68 -11
  96. package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
  97. package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
  98. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
  99. package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
  100. package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +6 -4
  101. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
  102. package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
  103. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
  104. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
  105. package/node_modules/undici/docs/docs/{GettingStarted.md → getting-started.md} +22 -19
  106. package/node_modules/undici/docs/docs/index.md +784 -0
  107. package/node_modules/undici/docs/docs/site.json +125 -0
  108. package/node_modules/undici/docs/docs/type-map.json +79 -0
  109. package/node_modules/undici/lib/api/api-request.js +7 -1
  110. package/node_modules/undici/lib/api/readable.js +47 -2
  111. package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  112. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  113. package/node_modules/undici/lib/core/errors.js +20 -0
  114. package/node_modules/undici/lib/core/request.js +14 -3
  115. package/node_modules/undici/lib/core/util.js +30 -2
  116. package/node_modules/undici/lib/dispatcher/agent.js +3 -3
  117. package/node_modules/undici/lib/dispatcher/client-h1.js +65 -8
  118. package/node_modules/undici/lib/dispatcher/client-h2.js +265 -142
  119. package/node_modules/undici/lib/dispatcher/proxy-agent.js +40 -6
  120. package/node_modules/undici/lib/global.js +36 -16
  121. package/node_modules/undici/lib/handler/cache-handler.js +269 -61
  122. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
  123. package/node_modules/undici/lib/handler/redirect-handler.js +6 -2
  124. package/node_modules/undici/lib/handler/retry-handler.js +87 -23
  125. package/node_modules/undici/lib/interceptor/cache.js +159 -52
  126. package/node_modules/undici/lib/interceptor/decompress.js +9 -2
  127. package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
  128. package/node_modules/undici/lib/mock/mock-utils.js +39 -10
  129. package/node_modules/undici/lib/util/cache.js +372 -68
  130. package/node_modules/undici/lib/util/date.js +23 -6
  131. package/node_modules/undici/lib/web/cookies/parse.js +3 -2
  132. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  133. package/node_modules/undici/lib/web/eventsource/util.js +1 -1
  134. package/node_modules/undici/lib/web/fetch/body.js +2 -1
  135. package/node_modules/undici/lib/web/fetch/constants.js +1 -1
  136. package/node_modules/undici/lib/web/fetch/index.js +11 -2
  137. package/node_modules/undici/lib/web/fetch/response.js +1 -3
  138. package/node_modules/undici/lib/web/fetch/util.js +4 -1
  139. package/node_modules/undici/package.json +2 -2
  140. package/node_modules/undici/types/client.d.ts +3 -3
  141. package/node_modules/undici/types/cookies.d.ts +1 -1
  142. package/node_modules/undici/types/dispatcher.d.ts +3 -3
  143. package/node_modules/undici/types/errors.d.ts +10 -0
  144. package/node_modules/undici/types/handlers.d.ts +2 -0
  145. package/node_modules/undici/types/mock-interceptor.d.ts +5 -1
  146. package/node_modules/undici/types/proxy-agent.d.ts +7 -0
  147. package/npm-shrinkwrap.json +7 -7
  148. package/package.json +5 -5
  149. package/dist/gateway-registry-DPxmW0Db.js +0 -74
  150. package/dist/provider.runtime-jNlHxUSW.js +0 -2
  151. package/dist/runtime-api.monitor-Bb5AaRzg.js +0 -6
  152. package/dist/targets-ClUPljCU.js +0 -3
@@ -292,25 +292,54 @@ function mockDispatch (opts, handler) {
292
292
  // Get mock dispatch from built key
293
293
  const key = buildKey(opts)
294
294
  const mockDispatch = getMockDispatch(this[kDispatches], key)
295
+ const mockDispatches = this[kDispatches]
295
296
 
296
297
  mockDispatch.timesInvoked++
297
298
 
299
+ const { timesInvoked, times } = mockDispatch
300
+
301
+ // If it's used up and not persistent, mark as consumed
302
+ mockDispatch.consumed = !mockDispatch.persist && timesInvoked >= times
303
+ mockDispatch.pending = timesInvoked < times
304
+
298
305
  // Here's where we resolve a callback if a callback is present for the dispatch data.
299
306
  if (mockDispatch.data.callback) {
300
- mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) }
307
+ const callbackResult = mockDispatch.data.callback(opts)
308
+
309
+ // An asynchronous reply options callback resolves to the reply data, so
310
+ // the dispatch can only continue once the returned promise settles.
311
+ // A rejection cannot be thrown synchronously from the dispatch at that
312
+ // point, so it is surfaced as a response error instead.
313
+ if (isPromise(callbackResult)) {
314
+ callbackResult.then(
315
+ (resolvedData) => {
316
+ mockDispatch.data = { ...mockDispatch.data, ...resolvedData }
317
+ dispatchMockReply(mockDispatches, mockDispatch, key, opts, handler)
318
+ },
319
+ (error) => {
320
+ deleteMockDispatch(mockDispatches, key)
321
+ handler.onResponseError(null, error)
322
+ }
323
+ )
324
+ return true
325
+ }
326
+
327
+ mockDispatch.data = { ...mockDispatch.data, ...callbackResult }
301
328
  }
302
329
 
303
- // Parse mockDispatch data
304
- const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch
305
- const { timesInvoked, times } = mockDispatch
330
+ return dispatchMockReply(mockDispatches, mockDispatch, key, opts, handler)
331
+ }
306
332
 
307
- // If it's used up and not persistent, mark as consumed
308
- mockDispatch.consumed = !persist && timesInvoked >= times
309
- mockDispatch.pending = timesInvoked < times
333
+ /**
334
+ * Replies to a request once the mock dispatch data is fully resolved
335
+ */
336
+ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler) {
337
+ // Parse mockDispatch data
338
+ const { data: { statusCode, data, headers, trailers, error }, delay } = mockDispatch
310
339
 
311
340
  // If specified, trigger dispatch error
312
341
  if (error !== null) {
313
- deleteMockDispatch(this[kDispatches], key)
342
+ deleteMockDispatch(mockDispatches, key)
314
343
  handler.onResponseError(null, error)
315
344
  return true
316
345
  }
@@ -353,10 +382,10 @@ function mockDispatch (opts, handler) {
353
382
  if (typeof delay === 'number' && delay > 0) {
354
383
  timer = setTimeout(() => {
355
384
  timer = null
356
- handleReply(this[kDispatches])
385
+ handleReply(mockDispatches)
357
386
  }, delay)
358
387
  } else {
359
- handleReply(this[kDispatches])
388
+ handleReply(mockDispatches)
360
389
  }
361
390
 
362
391
  function handleReply (mockDispatches, _data = data) {
@@ -3,11 +3,147 @@
3
3
  const {
4
4
  safeHTTPMethods,
5
5
  pathHasQueryOrFragment,
6
- hasSafeIterator
6
+ hasSafeIterator,
7
+ isValidHTTPToken
7
8
  } = require('../core/util')
8
9
 
9
10
  const { serializePathWithQuery } = require('../core/util')
10
11
 
12
+ const MAX_DELTA_SECONDS = 2147483647
13
+ const RESTRICTIVE_DIRECTIVE_NAMES = ['no-store', 'private', 'no-cache']
14
+ const kInvalidCacheControlDirectives = Symbol('invalid cache-control directives')
15
+
16
+ function trimOWS (value) {
17
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
18
+ }
19
+
20
+ function arrayIncludes (array, value) {
21
+ for (let i = 0; i < array.length; i++) {
22
+ if (array[i] === value) {
23
+ return true
24
+ }
25
+ }
26
+
27
+ return false
28
+ }
29
+
30
+ function trimOWSStart (value) {
31
+ return value.replace(/^[\t ]+/, '')
32
+ }
33
+
34
+ function trimOWSEnd (value) {
35
+ return value.replace(/[\t ]+$/, '')
36
+ }
37
+
38
+ function findUnescapedQuote (value, start) {
39
+ let escaped = false
40
+ for (let i = start; i < value.length; i++) {
41
+ if (escaped) {
42
+ escaped = false
43
+ } else if (value[i] === '\\') {
44
+ escaped = true
45
+ } else if (value[i] === '"') {
46
+ return i
47
+ }
48
+ }
49
+
50
+ return -1
51
+ }
52
+
53
+ function splitCacheControlHeaderValue (value) {
54
+ const directives = []
55
+ let start = 0
56
+ let quoteStart = -1
57
+ let inQuote = false
58
+ let escaped = false
59
+
60
+ for (let i = 0; i < value.length; i++) {
61
+ if (inQuote) {
62
+ if (escaped) {
63
+ escaped = false
64
+ } else if (value[i] === '\\') {
65
+ escaped = true
66
+ } else if (value[i] === '"') {
67
+ inQuote = false
68
+ quoteStart = -1
69
+ }
70
+ } else if (value[i] === '"') {
71
+ inQuote = true
72
+ quoteStart = i
73
+ } else if (value[i] === ',') {
74
+ directives.push({ value: value.substring(start, i), fromMalformedQuote: false })
75
+ start = i + 1
76
+ }
77
+ }
78
+
79
+ if (!inQuote) {
80
+ directives.push({ value: value.substring(start), fromMalformedQuote: false })
81
+ return directives
82
+ }
83
+
84
+ const tail = value.substring(start)
85
+ const quoteOffset = quoteStart - start
86
+ let tailStart = 0
87
+ for (let i = 0; i < tail.length; i++) {
88
+ if (tail[i] === ',') {
89
+ directives.push({
90
+ value: tail.substring(tailStart, i),
91
+ fromMalformedQuote: tailStart > quoteOffset
92
+ })
93
+ tailStart = i + 1
94
+ }
95
+ }
96
+
97
+ directives.push({
98
+ value: tail.substring(tailStart),
99
+ fromMalformedQuote: tailStart > quoteOffset
100
+ })
101
+ return directives
102
+ }
103
+
104
+ function markInvalidCacheControlDirective (directives, key) {
105
+ let invalidDirectives = directives[kInvalidCacheControlDirectives]
106
+
107
+ if (invalidDirectives === undefined) {
108
+ invalidDirectives = new Set()
109
+ Object.defineProperty(directives, kInvalidCacheControlDirectives, {
110
+ value: invalidDirectives
111
+ })
112
+ }
113
+
114
+ invalidDirectives.add(key)
115
+ }
116
+
117
+ function hasInvalidCacheControlDirective (directives, key) {
118
+ return directives[kInvalidCacheControlDirectives]?.has(key) === true
119
+ }
120
+
121
+ function getMalformedRestrictiveDirectiveName (key) {
122
+ for (const directiveName of RESTRICTIVE_DIRECTIVE_NAMES) {
123
+ if (
124
+ key.startsWith(directiveName) &&
125
+ key.length > directiveName.length &&
126
+ !isValidHTTPToken(key[directiveName.length])
127
+ ) {
128
+ return directiveName
129
+ }
130
+ }
131
+
132
+ let tokenOnlyKey = ''
133
+ let hasInvalidTokenChar = false
134
+ for (let i = 0; i < key.length; i++) {
135
+ if (isValidHTTPToken(key[i])) {
136
+ tokenOnlyKey += key[i]
137
+ } else {
138
+ hasInvalidTokenChar = true
139
+ }
140
+ }
141
+
142
+ if (hasInvalidTokenChar && arrayIncludes(RESTRICTIVE_DIRECTIVE_NAMES, tokenOnlyKey)) {
143
+ return tokenOnlyKey
144
+ }
145
+ }
146
+
11
147
  /**
12
148
  * @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
13
149
  */
@@ -30,6 +166,20 @@ function makeCacheKey (opts) {
30
166
  }
31
167
  }
32
168
 
169
+ function appendHeader (headers, key, val) {
170
+ const headerName = key.toLowerCase()
171
+ const current = headers[headerName]
172
+ const values = Array.isArray(val) ? val : [val]
173
+
174
+ if (current === undefined) {
175
+ headers[headerName] = Array.isArray(val) ? val.slice() : val
176
+ } else if (Array.isArray(current)) {
177
+ current.push(...values)
178
+ } else {
179
+ headers[headerName] = [current, ...values]
180
+ }
181
+ }
182
+
33
183
  /**
34
184
  * @param {Record<string, string[] | string>}
35
185
  * @returns {Record<string, string[] | string>}
@@ -42,19 +192,61 @@ function normalizeHeaders (opts) {
42
192
  headers = {}
43
193
 
44
194
  if (hasSafeIterator(opts.headers)) {
45
- for (const x of opts.headers) {
46
- if (!Array.isArray(x)) {
47
- throw new Error('opts.headers is not a valid header map')
195
+ if (Array.isArray(opts.headers)) {
196
+ // Array format: could be flat alternating [k, v, k, v, ...]
197
+ // or array-of-pairs [[k, v], ...]
198
+ const first = opts.headers[0]
199
+ if (Array.isArray(first)) {
200
+ for (const x of opts.headers) {
201
+ if (!Array.isArray(x)) {
202
+ throw new Error('opts.headers is not a valid header map')
203
+ }
204
+ const [key, val] = x
205
+ if (typeof key !== 'string' || typeof val !== 'string') {
206
+ throw new Error('opts.headers is not a valid header map')
207
+ }
208
+ appendHeader(headers, key, val)
209
+ }
210
+ } else {
211
+ // Flat alternating array [k, v, k, v, ...]
212
+ const len = opts.headers.length
213
+ if (len % 2 !== 0) {
214
+ throw new Error('opts.headers is not a valid header map')
215
+ }
216
+ for (let i = 0; i < len; i += 2) {
217
+ const key = opts.headers[i]
218
+ const val = opts.headers[i + 1]
219
+ if (typeof key !== 'string' || (typeof val !== 'string' && !Array.isArray(val))) {
220
+ throw new Error('opts.headers is not a valid header map')
221
+ }
222
+ if (typeof val === 'string') {
223
+ appendHeader(headers, key, val)
224
+ } else {
225
+ const mapped = []
226
+ for (let j = 0; j < val.length; j++) {
227
+ const v = val[j]
228
+ mapped.push(typeof v === 'string' ? v : v.toString('latin1'))
229
+ }
230
+ appendHeader(headers, key, mapped)
231
+ }
232
+ }
48
233
  }
49
- const [key, val] = x
50
- if (typeof key !== 'string' || typeof val !== 'string') {
51
- throw new Error('opts.headers is not a valid header map')
234
+ } else {
235
+ // Non-array iterable (e.g. Map) — use original iteration logic
236
+ for (const x of opts.headers) {
237
+ if (!Array.isArray(x)) {
238
+ throw new Error('opts.headers is not a valid header map')
239
+ }
240
+ const [key, val] = x
241
+ if (typeof key !== 'string' || typeof val !== 'string') {
242
+ throw new Error('opts.headers is not a valid header map')
243
+ }
244
+ appendHeader(headers, key, val)
52
245
  }
53
- headers[key.toLowerCase()] = val
54
246
  }
55
247
  } else {
56
248
  for (const key of Object.keys(opts.headers)) {
57
- headers[key.toLowerCase()] = opts.headers[key]
249
+ appendHeader(headers, key, opts.headers[key])
58
250
  }
59
251
  }
60
252
  } else {
@@ -126,29 +318,37 @@ function parseCacheControlHeader (header) {
126
318
  * @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
127
319
  */
128
320
  const output = {}
321
+ const invalidNumericDirectives = new Set()
322
+ const invalidNoArgumentDirectives = new Set()
129
323
 
130
- let directives
131
- if (Array.isArray(header)) {
132
- directives = []
133
-
134
- for (const directive of header) {
135
- directives.push(...directive.split(','))
136
- }
137
- } else {
138
- directives = header.split(',')
139
- }
324
+ const directives = splitCacheControlHeaderValue(Array.isArray(header) ? header.join(',') : header)
140
325
 
141
326
  for (let i = 0; i < directives.length; i++) {
142
- const directive = directives[i].toLowerCase()
327
+ const directiveRecord = directives[i]
328
+ const directive = directiveRecord.value.toLowerCase()
329
+ const fromMalformedQuote = directiveRecord.fromMalformedQuote
143
330
  const keyValueDelimiter = directive.indexOf('=')
144
331
 
145
332
  let key
146
333
  let value
334
+ let keyHasTrailingWhitespace = false
335
+ let valueHasLeadingWhitespace = false
147
336
  if (keyValueDelimiter !== -1) {
148
- key = directive.substring(0, keyValueDelimiter).trimStart()
149
- value = directive.substring(keyValueDelimiter + 1)
337
+ const rawKey = directive.substring(0, keyValueDelimiter)
338
+ const rawValue = directive.substring(keyValueDelimiter + 1)
339
+
340
+ keyHasTrailingWhitespace = trimOWSEnd(rawKey) !== rawKey
341
+ valueHasLeadingWhitespace = trimOWSStart(rawValue) !== rawValue
342
+ key = trimOWS(rawKey)
343
+ value = trimOWSStart(rawValue)
150
344
  } else {
151
- key = directive.trim()
345
+ key = trimOWS(directive)
346
+ }
347
+
348
+ const malformedRestrictiveDirectiveName = getMalformedRestrictiveDirectiveName(key)
349
+ if (malformedRestrictiveDirectiveName !== undefined) {
350
+ output[malformedRestrictiveDirectiveName] = true
351
+ continue
152
352
  }
153
353
 
154
354
  switch (key) {
@@ -158,7 +358,14 @@ function parseCacheControlHeader (header) {
158
358
  case 's-maxage':
159
359
  case 'stale-while-revalidate':
160
360
  case 'stale-if-error': {
161
- if (value === undefined || value[0] === ' ') {
361
+ if (fromMalformedQuote || invalidNumericDirectives.has(key)) {
362
+ continue
363
+ }
364
+
365
+ if (value === undefined || keyHasTrailingWhitespace || valueHasLeadingWhitespace) {
366
+ delete output[key]
367
+ invalidNumericDirectives.add(key)
368
+ markInvalidCacheControlDirective(output, key)
162
369
  continue
163
370
  }
164
371
 
@@ -170,22 +377,37 @@ function parseCacheControlHeader (header) {
170
377
  value = value.substring(1, value.length - 1)
171
378
  }
172
379
 
173
- const parsedValue = parseInt(value, 10)
174
- // eslint-disable-next-line no-self-compare
175
- if (parsedValue !== parsedValue) {
380
+ if (!/^[0-9]+$/.test(value)) {
381
+ delete output[key]
382
+ invalidNumericDirectives.add(key)
383
+ markInvalidCacheControlDirective(output, key)
176
384
  continue
177
385
  }
178
386
 
179
- if (key === 'max-age' && key in output && output[key] >= parsedValue) {
180
- continue
181
- }
387
+ const parsedValue = Math.min(parseInt(value, 10), MAX_DELTA_SECONDS)
182
388
 
183
- output[key] = parsedValue
389
+ if (key === 'min-fresh') {
390
+ if (!(key in output) || output[key] < parsedValue) {
391
+ output[key] = parsedValue
392
+ }
393
+ } else if (!(key in output) || output[key] > parsedValue) {
394
+ output[key] = parsedValue
395
+ }
184
396
 
185
397
  break
186
398
  }
187
399
  case 'private':
188
400
  case 'no-cache': {
401
+ if (fromMalformedQuote) {
402
+ output[key] = true
403
+ break
404
+ }
405
+
406
+ if (value !== undefined && value.length === 0) {
407
+ output[key] = true
408
+ break
409
+ }
410
+
189
411
  if (value) {
190
412
  // The private and no-cache directives can be unqualified (aka just
191
413
  // `private` or `no-cache`) or qualified (w/ a value). When they're
@@ -193,45 +415,64 @@ function parseCacheControlHeader (header) {
193
415
  // `no-cache="header1"`, or `no-cache="header1, header2"`
194
416
  // If we're given multiple headers, the comma messes us up since
195
417
  // we split the full header by commas. So, let's loop through the
196
- // remaining parts in front of us until we find one that ends in a
197
- // quote. We can then just splice all of the parts in between the
198
- // starting quote and the ending quote out of the directives array
199
- // and continue parsing like normal.
418
+ // remaining parts in front of us until we find one that contains a
419
+ // closing quote. We can then skip the consumed quoted-list fragments and
420
+ // continue parsing like normal.
200
421
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
201
422
  if (value[0] === '"') {
202
423
  // Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
424
+ value = trimOWSEnd(value)
203
425
 
204
- // Add the first header on and cut off the leading quote
205
- const headers = [value.substring(1)]
426
+ let fieldList = ''
427
+ let lastQuotedPart = i
428
+ let foundEndingQuote = false
429
+ const closingQuote = findUnescapedQuote(value, 1)
206
430
 
207
- let foundEndingQuote = value[value.length - 1] === '"'
208
- if (!foundEndingQuote) {
431
+ if (closingQuote !== -1) {
432
+ fieldList = value.substring(1, closingQuote)
433
+ foundEndingQuote = true
434
+ } else {
209
435
  // Something like `no-cache="some-header, another-header"`
210
436
  // This can still be something invalid, e.g. `no-cache="some-header, ...`
437
+ const fieldListParts = [value.substring(1)]
438
+
211
439
  for (let j = i + 1; j < directives.length; j++) {
212
- const nextPart = directives[j]
213
- const nextPartLength = nextPart.length
440
+ const nextPart = trimOWS(directives[j].value)
441
+ const closingQuote = findUnescapedQuote(nextPart, 0)
214
442
 
215
- headers.push(nextPart.trim())
443
+ lastQuotedPart = j
216
444
 
217
- if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
445
+ if (closingQuote !== -1) {
446
+ fieldListParts.push(nextPart.substring(0, closingQuote))
218
447
  foundEndingQuote = true
219
448
  break
220
449
  }
450
+
451
+ fieldListParts.push(nextPart)
221
452
  }
453
+
454
+ fieldList = fieldListParts.join(',')
222
455
  }
223
456
 
224
- if (foundEndingQuote) {
225
- let lastHeader = headers[headers.length - 1]
226
- if (lastHeader[lastHeader.length - 1] === '"') {
227
- lastHeader = lastHeader.substring(0, lastHeader.length - 1)
228
- headers[headers.length - 1] = lastHeader
229
- }
457
+ if (!foundEndingQuote) {
458
+ output[key] = true
459
+ break
460
+ }
230
461
 
231
- for (let j = 0; j < headers.length; j++) {
232
- headers[j] = headers[j].trim()
462
+ i = lastQuotedPart
463
+
464
+ const headers = fieldList.split(',')
465
+ let validFieldNames = true
466
+ for (let j = 0; j < headers.length; j++) {
467
+ headers[j] = trimOWS(headers[j])
468
+ if (!isValidHTTPToken(headers[j])) {
469
+ validFieldNames = false
233
470
  }
471
+ }
234
472
 
473
+ if (!validFieldNames) {
474
+ output[key] = true
475
+ } else if (output[key] !== true) {
235
476
  if (key in output) {
236
477
  output[key] = output[key].concat(headers)
237
478
  } else {
@@ -239,13 +480,17 @@ function parseCacheControlHeader (header) {
239
480
  }
240
481
  }
241
482
  } else {
242
- // Something like `no-cache="some-header"`
243
- const fieldName = value.trim()
483
+ // Something like `no-cache=some-header`
484
+ const fieldName = trimOWS(value)
244
485
 
245
- if (key in output) {
246
- output[key] = output[key].concat(fieldName)
247
- } else {
248
- output[key] = [fieldName]
486
+ if (!isValidHTTPToken(fieldName)) {
487
+ output[key] = true
488
+ } else if (output[key] !== true) {
489
+ if (key in output) {
490
+ output[key] = output[key].concat(fieldName)
491
+ } else {
492
+ output[key] = [fieldName]
493
+ }
249
494
  }
250
495
  }
251
496
 
@@ -254,19 +499,27 @@ function parseCacheControlHeader (header) {
254
499
  }
255
500
  // eslint-disable-next-line no-fallthrough
256
501
  case 'public':
257
- case 'no-store':
258
502
  case 'must-revalidate':
259
503
  case 'proxy-revalidate':
260
504
  case 'immutable':
261
505
  case 'no-transform':
262
506
  case 'must-understand':
263
507
  case 'only-if-cached':
264
- if (value) {
508
+ if (fromMalformedQuote || invalidNoArgumentDirectives.has(key)) {
509
+ continue
510
+ }
511
+
512
+ if (value !== undefined) {
265
513
  // These are qualified (something like `public=...`) when they aren't
266
- // allowed to be, skip
514
+ // allowed to be, skip all instances of the malformed directive.
515
+ delete output[key]
516
+ invalidNoArgumentDirectives.add(key)
267
517
  continue
268
518
  }
269
519
 
520
+ output[key] = true
521
+ break
522
+ case 'no-store':
270
523
  output[key] = true
271
524
  break
272
525
  default:
@@ -278,31 +531,79 @@ function parseCacheControlHeader (header) {
278
531
  return output
279
532
  }
280
533
 
534
+ /**
535
+ * @param {string | string[]} varyHeader Vary header from the server
536
+ * @returns {string[]}
537
+ */
538
+ function splitVaryHeader (varyHeader) {
539
+ const values = Array.isArray(varyHeader) ? varyHeader : [varyHeader]
540
+ const output = []
541
+
542
+ for (let i = 0; i < values.length; i++) {
543
+ const parts = values[i].split(',')
544
+ for (let j = 0; j < parts.length; j++) {
545
+ output.push(parts[j])
546
+ }
547
+ }
548
+
549
+ return output
550
+ }
551
+
552
+ /**
553
+ * @param {string | string[]} varyHeader Vary header from the server
554
+ * @returns {boolean}
555
+ */
556
+ function hasVaryStar (varyHeader) {
557
+ const values = splitVaryHeader(varyHeader)
558
+ for (let i = 0; i < values.length; i++) {
559
+ if (trimOWS(values[i]).indexOf('*') !== -1) {
560
+ return true
561
+ }
562
+ }
563
+
564
+ return false
565
+ }
566
+
281
567
  /**
282
568
  * @param {string | string[]} varyHeader Vary header from the server
283
569
  * @param {Record<string, string | string[]>} headers Request headers
284
- * @returns {Record<string, string | string[]>}
570
+ * @returns {Record<string, string | string[] | null> | undefined}
285
571
  */
286
572
  function parseVaryHeader (varyHeader, headers) {
287
- if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
573
+ if (hasVaryStar(varyHeader)) {
288
574
  return headers
289
575
  }
290
576
 
291
577
  const output = /** @type {Record<string, string | string[] | null>} */ ({})
292
578
 
293
- const varyingHeaders = typeof varyHeader === 'string'
294
- ? varyHeader.split(',')
295
- : varyHeader
579
+ const varyingHeaders = splitVaryHeader(varyHeader)
296
580
 
297
581
  for (const header of varyingHeaders) {
298
- const trimmedHeader = header.trim().toLowerCase()
582
+ const trimmedHeader = trimOWS(header).toLowerCase()
583
+
584
+ if (trimmedHeader.length === 0) {
585
+ continue
586
+ }
299
587
 
300
- output[trimmedHeader] = headers[trimmedHeader] ?? null
588
+ if (!isValidHTTPToken(trimmedHeader)) {
589
+ return undefined
590
+ }
591
+
592
+ const headerValue = headers[trimmedHeader]
593
+ output[trimmedHeader] = Array.isArray(headerValue) ? headerValue.slice() : headerValue ?? null
301
594
  }
302
595
 
303
596
  return output
304
597
  }
305
598
 
599
+ /**
600
+ * @param {string | string[]} varyHeader Vary header from the server
601
+ * @returns {boolean}
602
+ */
603
+ function isInvalidOrWildcardVaryHeader (varyHeader) {
604
+ return hasVaryStar(varyHeader) || parseVaryHeader(varyHeader, {}) === undefined
605
+ }
606
+
306
607
  /**
307
608
  * Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
308
609
  * however, including them in cached resposnes serves little to no purpose.
@@ -366,7 +667,7 @@ function assertCacheMethods (methods, name = 'CacheMethods') {
366
667
  }
367
668
 
368
669
  for (const method of methods) {
369
- if (!safeHTTPMethods.includes(method)) {
670
+ if (!arrayIncludes(safeHTTPMethods, method)) {
370
671
  throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
371
672
  }
372
673
  }
@@ -406,7 +707,10 @@ module.exports = {
406
707
  assertCacheKey,
407
708
  assertCacheValue,
408
709
  parseCacheControlHeader,
710
+ hasInvalidCacheControlDirective,
409
711
  parseVaryHeader,
712
+ hasVaryStar,
713
+ isInvalidOrWildcardVaryHeader,
410
714
  isEtagUsable,
411
715
  assertCacheMethods,
412
716
  assertCacheStore,