@depup/undici 7.24.3-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/changes.json +5 -0
  4. package/docs/docs/api/Agent.md +84 -0
  5. package/docs/docs/api/BalancedPool.md +99 -0
  6. package/docs/docs/api/CacheStorage.md +30 -0
  7. package/docs/docs/api/CacheStore.md +164 -0
  8. package/docs/docs/api/Client.md +285 -0
  9. package/docs/docs/api/ClientStats.md +27 -0
  10. package/docs/docs/api/Connector.md +115 -0
  11. package/docs/docs/api/ContentType.md +57 -0
  12. package/docs/docs/api/Cookies.md +101 -0
  13. package/docs/docs/api/Debug.md +62 -0
  14. package/docs/docs/api/DiagnosticsChannel.md +313 -0
  15. package/docs/docs/api/Dispatcher.md +1392 -0
  16. package/docs/docs/api/EnvHttpProxyAgent.md +159 -0
  17. package/docs/docs/api/Errors.md +49 -0
  18. package/docs/docs/api/EventSource.md +45 -0
  19. package/docs/docs/api/Fetch.md +52 -0
  20. package/docs/docs/api/GlobalInstallation.md +91 -0
  21. package/docs/docs/api/H2CClient.md +263 -0
  22. package/docs/docs/api/MockAgent.md +603 -0
  23. package/docs/docs/api/MockCallHistory.md +197 -0
  24. package/docs/docs/api/MockCallHistoryLog.md +43 -0
  25. package/docs/docs/api/MockClient.md +81 -0
  26. package/docs/docs/api/MockErrors.md +12 -0
  27. package/docs/docs/api/MockPool.md +555 -0
  28. package/docs/docs/api/Pool.md +84 -0
  29. package/docs/docs/api/PoolStats.md +35 -0
  30. package/docs/docs/api/ProxyAgent.md +229 -0
  31. package/docs/docs/api/RedirectHandler.md +96 -0
  32. package/docs/docs/api/RetryAgent.md +50 -0
  33. package/docs/docs/api/RetryHandler.md +118 -0
  34. package/docs/docs/api/RoundRobinPool.md +145 -0
  35. package/docs/docs/api/SnapshotAgent.md +616 -0
  36. package/docs/docs/api/Socks5ProxyAgent.md +274 -0
  37. package/docs/docs/api/Util.md +25 -0
  38. package/docs/docs/api/WebSocket.md +141 -0
  39. package/docs/docs/api/api-lifecycle.md +91 -0
  40. package/docs/docs/best-practices/client-certificate.md +64 -0
  41. package/docs/docs/best-practices/crawling.md +58 -0
  42. package/docs/docs/best-practices/mocking-request.md +190 -0
  43. package/docs/docs/best-practices/proxy.md +127 -0
  44. package/docs/docs/best-practices/undici-vs-builtin-fetch.md +137 -0
  45. package/docs/docs/best-practices/writing-tests.md +20 -0
  46. package/index-fetch.js +65 -0
  47. package/index.d.ts +3 -0
  48. package/index.js +234 -0
  49. package/lib/api/abort-signal.js +59 -0
  50. package/lib/api/api-connect.js +110 -0
  51. package/lib/api/api-pipeline.js +252 -0
  52. package/lib/api/api-request.js +214 -0
  53. package/lib/api/api-stream.js +209 -0
  54. package/lib/api/api-upgrade.js +111 -0
  55. package/lib/api/index.js +7 -0
  56. package/lib/api/readable.js +580 -0
  57. package/lib/cache/memory-cache-store.js +234 -0
  58. package/lib/cache/sqlite-cache-store.js +461 -0
  59. package/lib/core/connect.js +137 -0
  60. package/lib/core/constants.js +143 -0
  61. package/lib/core/diagnostics.js +225 -0
  62. package/lib/core/errors.js +477 -0
  63. package/lib/core/request.js +430 -0
  64. package/lib/core/socks5-client.js +407 -0
  65. package/lib/core/socks5-utils.js +203 -0
  66. package/lib/core/symbols.js +75 -0
  67. package/lib/core/tree.js +160 -0
  68. package/lib/core/util.js +972 -0
  69. package/lib/dispatcher/agent.js +158 -0
  70. package/lib/dispatcher/balanced-pool.js +219 -0
  71. package/lib/dispatcher/client-h1.js +1610 -0
  72. package/lib/dispatcher/client-h2.js +995 -0
  73. package/lib/dispatcher/client.js +654 -0
  74. package/lib/dispatcher/dispatcher-base.js +165 -0
  75. package/lib/dispatcher/dispatcher.js +48 -0
  76. package/lib/dispatcher/env-http-proxy-agent.js +146 -0
  77. package/lib/dispatcher/fixed-queue.js +135 -0
  78. package/lib/dispatcher/h2c-client.js +51 -0
  79. package/lib/dispatcher/pool-base.js +214 -0
  80. package/lib/dispatcher/pool.js +118 -0
  81. package/lib/dispatcher/proxy-agent.js +318 -0
  82. package/lib/dispatcher/retry-agent.js +35 -0
  83. package/lib/dispatcher/round-robin-pool.js +137 -0
  84. package/lib/dispatcher/socks5-proxy-agent.js +249 -0
  85. package/lib/encoding/index.js +33 -0
  86. package/lib/global.js +50 -0
  87. package/lib/handler/cache-handler.js +561 -0
  88. package/lib/handler/cache-revalidation-handler.js +124 -0
  89. package/lib/handler/decorator-handler.js +67 -0
  90. package/lib/handler/deduplication-handler.js +460 -0
  91. package/lib/handler/redirect-handler.js +238 -0
  92. package/lib/handler/retry-handler.js +394 -0
  93. package/lib/handler/unwrap-handler.js +100 -0
  94. package/lib/handler/wrap-handler.js +105 -0
  95. package/lib/interceptor/cache.js +495 -0
  96. package/lib/interceptor/decompress.js +259 -0
  97. package/lib/interceptor/deduplicate.js +117 -0
  98. package/lib/interceptor/dns.js +571 -0
  99. package/lib/interceptor/dump.js +112 -0
  100. package/lib/interceptor/redirect.js +21 -0
  101. package/lib/interceptor/response-error.js +95 -0
  102. package/lib/interceptor/retry.js +19 -0
  103. package/lib/llhttp/.gitkeep +0 -0
  104. package/lib/llhttp/constants.d.ts +195 -0
  105. package/lib/llhttp/constants.js +531 -0
  106. package/lib/llhttp/llhttp-wasm.js +15 -0
  107. package/lib/llhttp/llhttp_simd-wasm.js +15 -0
  108. package/lib/llhttp/utils.d.ts +2 -0
  109. package/lib/llhttp/utils.js +12 -0
  110. package/lib/mock/mock-agent.js +232 -0
  111. package/lib/mock/mock-call-history.js +248 -0
  112. package/lib/mock/mock-client.js +68 -0
  113. package/lib/mock/mock-errors.js +29 -0
  114. package/lib/mock/mock-interceptor.js +209 -0
  115. package/lib/mock/mock-pool.js +68 -0
  116. package/lib/mock/mock-symbols.js +31 -0
  117. package/lib/mock/mock-utils.js +480 -0
  118. package/lib/mock/pending-interceptors-formatter.js +43 -0
  119. package/lib/mock/snapshot-agent.js +353 -0
  120. package/lib/mock/snapshot-recorder.js +588 -0
  121. package/lib/mock/snapshot-utils.js +158 -0
  122. package/lib/util/cache.js +407 -0
  123. package/lib/util/date.js +653 -0
  124. package/lib/util/promise.js +28 -0
  125. package/lib/util/runtime-features.js +124 -0
  126. package/lib/util/stats.js +32 -0
  127. package/lib/util/timers.js +425 -0
  128. package/lib/web/cache/cache.js +864 -0
  129. package/lib/web/cache/cachestorage.js +152 -0
  130. package/lib/web/cache/util.js +45 -0
  131. package/lib/web/cookies/constants.js +12 -0
  132. package/lib/web/cookies/index.js +199 -0
  133. package/lib/web/cookies/parse.js +322 -0
  134. package/lib/web/cookies/util.js +282 -0
  135. package/lib/web/eventsource/eventsource-stream.js +399 -0
  136. package/lib/web/eventsource/eventsource.js +501 -0
  137. package/lib/web/eventsource/util.js +29 -0
  138. package/lib/web/fetch/LICENSE +21 -0
  139. package/lib/web/fetch/body.js +509 -0
  140. package/lib/web/fetch/constants.js +131 -0
  141. package/lib/web/fetch/data-url.js +596 -0
  142. package/lib/web/fetch/formdata-parser.js +575 -0
  143. package/lib/web/fetch/formdata.js +259 -0
  144. package/lib/web/fetch/global.js +40 -0
  145. package/lib/web/fetch/headers.js +719 -0
  146. package/lib/web/fetch/index.js +2378 -0
  147. package/lib/web/fetch/request.js +1115 -0
  148. package/lib/web/fetch/response.js +641 -0
  149. package/lib/web/fetch/util.js +1520 -0
  150. package/lib/web/infra/index.js +229 -0
  151. package/lib/web/subresource-integrity/Readme.md +9 -0
  152. package/lib/web/subresource-integrity/subresource-integrity.js +307 -0
  153. package/lib/web/webidl/index.js +1006 -0
  154. package/lib/web/websocket/connection.js +329 -0
  155. package/lib/web/websocket/constants.js +126 -0
  156. package/lib/web/websocket/events.js +331 -0
  157. package/lib/web/websocket/frame.js +133 -0
  158. package/lib/web/websocket/permessage-deflate.js +118 -0
  159. package/lib/web/websocket/receiver.js +450 -0
  160. package/lib/web/websocket/sender.js +109 -0
  161. package/lib/web/websocket/stream/websocketerror.js +104 -0
  162. package/lib/web/websocket/stream/websocketstream.js +497 -0
  163. package/lib/web/websocket/util.js +347 -0
  164. package/lib/web/websocket/websocket.js +739 -0
  165. package/package.json +163 -0
  166. package/scripts/strip-comments.js +10 -0
  167. package/types/README.md +6 -0
  168. package/types/agent.d.ts +32 -0
  169. package/types/api.d.ts +43 -0
  170. package/types/balanced-pool.d.ts +30 -0
  171. package/types/cache-interceptor.d.ts +179 -0
  172. package/types/cache.d.ts +36 -0
  173. package/types/client-stats.d.ts +15 -0
  174. package/types/client.d.ts +123 -0
  175. package/types/connector.d.ts +36 -0
  176. package/types/content-type.d.ts +21 -0
  177. package/types/cookies.d.ts +30 -0
  178. package/types/diagnostics-channel.d.ts +74 -0
  179. package/types/dispatcher.d.ts +279 -0
  180. package/types/env-http-proxy-agent.d.ts +22 -0
  181. package/types/errors.d.ts +177 -0
  182. package/types/eventsource.d.ts +66 -0
  183. package/types/fetch.d.ts +211 -0
  184. package/types/formdata.d.ts +108 -0
  185. package/types/global-dispatcher.d.ts +9 -0
  186. package/types/global-origin.d.ts +7 -0
  187. package/types/h2c-client.d.ts +73 -0
  188. package/types/handlers.d.ts +15 -0
  189. package/types/header.d.ts +160 -0
  190. package/types/index.d.ts +91 -0
  191. package/types/interceptors.d.ts +80 -0
  192. package/types/mock-agent.d.ts +68 -0
  193. package/types/mock-call-history.d.ts +111 -0
  194. package/types/mock-client.d.ts +27 -0
  195. package/types/mock-errors.d.ts +12 -0
  196. package/types/mock-interceptor.d.ts +94 -0
  197. package/types/mock-pool.d.ts +27 -0
  198. package/types/patch.d.ts +29 -0
  199. package/types/pool-stats.d.ts +19 -0
  200. package/types/pool.d.ts +41 -0
  201. package/types/proxy-agent.d.ts +29 -0
  202. package/types/readable.d.ts +68 -0
  203. package/types/retry-agent.d.ts +8 -0
  204. package/types/retry-handler.d.ts +125 -0
  205. package/types/round-robin-pool.d.ts +41 -0
  206. package/types/snapshot-agent.d.ts +109 -0
  207. package/types/socks5-proxy-agent.d.ts +25 -0
  208. package/types/util.d.ts +18 -0
  209. package/types/utility.d.ts +7 -0
  210. package/types/webidl.d.ts +347 -0
  211. package/types/websocket.d.ts +188 -0
@@ -0,0 +1,972 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { kDestroyed, kBodyUsed, kListeners, kBody } = require('./symbols')
5
+ const { IncomingMessage } = require('node:http')
6
+ const stream = require('node:stream')
7
+ const net = require('node:net')
8
+ const { stringify } = require('node:querystring')
9
+ const { EventEmitter: EE } = require('node:events')
10
+ const timers = require('../util/timers')
11
+ const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
12
+ const { headerNameLowerCasedRecord } = require('./constants')
13
+ const { tree } = require('./tree')
14
+
15
+ const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(v => Number(v))
16
+
17
+ class BodyAsyncIterable {
18
+ constructor (body) {
19
+ this[kBody] = body
20
+ this[kBodyUsed] = false
21
+ }
22
+
23
+ async * [Symbol.asyncIterator] () {
24
+ assert(!this[kBodyUsed], 'disturbed')
25
+ this[kBodyUsed] = true
26
+ yield * this[kBody]
27
+ }
28
+ }
29
+
30
+ function noop () {}
31
+
32
+ /**
33
+ * @param {*} body
34
+ * @returns {*}
35
+ */
36
+ function wrapRequestBody (body) {
37
+ if (isStream(body)) {
38
+ // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
39
+ // so that it can be dispatched again?
40
+ // TODO (fix): Do we need 100-expect support to provide a way to do this properly?
41
+ if (bodyLength(body) === 0) {
42
+ body
43
+ .on('data', function () {
44
+ assert(false)
45
+ })
46
+ }
47
+
48
+ if (typeof body.readableDidRead !== 'boolean') {
49
+ body[kBodyUsed] = false
50
+ EE.prototype.on.call(body, 'data', function () {
51
+ this[kBodyUsed] = true
52
+ })
53
+ }
54
+
55
+ return body
56
+ } else if (body && typeof body.pipeTo === 'function') {
57
+ // TODO (fix): We can't access ReadableStream internal state
58
+ // to determine whether or not it has been disturbed. This is just
59
+ // a workaround.
60
+ return new BodyAsyncIterable(body)
61
+ } else if (body && isFormDataLike(body)) {
62
+ return body
63
+ } else if (
64
+ body &&
65
+ typeof body !== 'string' &&
66
+ !ArrayBuffer.isView(body) &&
67
+ isIterable(body)
68
+ ) {
69
+ // TODO: Should we allow re-using iterable if !this.opts.idempotent
70
+ // or through some other flag?
71
+ return new BodyAsyncIterable(body)
72
+ } else {
73
+ return body
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @param {*} obj
79
+ * @returns {obj is import('node:stream').Stream}
80
+ */
81
+ function isStream (obj) {
82
+ return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
83
+ }
84
+
85
+ /**
86
+ * @param {*} object
87
+ * @returns {object is Blob}
88
+ * based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
89
+ */
90
+ function isBlobLike (object) {
91
+ if (object === null) {
92
+ return false
93
+ } else if (object instanceof Blob) {
94
+ return true
95
+ } else if (typeof object !== 'object') {
96
+ return false
97
+ } else {
98
+ const sTag = object[Symbol.toStringTag]
99
+
100
+ return (sTag === 'Blob' || sTag === 'File') && (
101
+ ('stream' in object && typeof object.stream === 'function') ||
102
+ ('arrayBuffer' in object && typeof object.arrayBuffer === 'function')
103
+ )
104
+ }
105
+ }
106
+
107
+ /**
108
+ * @param {string} url The path to check for query strings or fragments.
109
+ * @returns {boolean} Returns true if the path contains a query string or fragment.
110
+ */
111
+ function pathHasQueryOrFragment (url) {
112
+ return (
113
+ url.includes('?') ||
114
+ url.includes('#')
115
+ )
116
+ }
117
+
118
+ /**
119
+ * @param {string} url The URL to add the query params to
120
+ * @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string
121
+ * @returns {string} The URL with the query params added
122
+ */
123
+ function serializePathWithQuery (url, queryParams) {
124
+ if (pathHasQueryOrFragment(url)) {
125
+ throw new Error('Query params cannot be passed when url already contains "?" or "#".')
126
+ }
127
+
128
+ const stringified = stringify(queryParams)
129
+
130
+ if (stringified) {
131
+ url += '?' + stringified
132
+ }
133
+
134
+ return url
135
+ }
136
+
137
+ /**
138
+ * @param {number|string|undefined} port
139
+ * @returns {boolean}
140
+ */
141
+ function isValidPort (port) {
142
+ const value = parseInt(port, 10)
143
+ return (
144
+ value === Number(port) &&
145
+ value >= 0 &&
146
+ value <= 65535
147
+ )
148
+ }
149
+
150
+ /**
151
+ * Check if the value is a valid http or https prefixed string.
152
+ *
153
+ * @param {string} value
154
+ * @returns {boolean}
155
+ */
156
+ function isHttpOrHttpsPrefixed (value) {
157
+ return (
158
+ value != null &&
159
+ value[0] === 'h' &&
160
+ value[1] === 't' &&
161
+ value[2] === 't' &&
162
+ value[3] === 'p' &&
163
+ (
164
+ value[4] === ':' ||
165
+ (
166
+ value[4] === 's' &&
167
+ value[5] === ':'
168
+ )
169
+ )
170
+ )
171
+ }
172
+
173
+ /**
174
+ * @param {string|URL|Record<string,string>} url
175
+ * @returns {URL}
176
+ */
177
+ function parseURL (url) {
178
+ if (typeof url === 'string') {
179
+ /**
180
+ * @type {URL}
181
+ */
182
+ url = new URL(url)
183
+
184
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
185
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
186
+ }
187
+
188
+ return url
189
+ }
190
+
191
+ if (!url || typeof url !== 'object') {
192
+ throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
193
+ }
194
+
195
+ if (!(url instanceof URL)) {
196
+ if (url.port != null && url.port !== '' && isValidPort(url.port) === false) {
197
+ throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.')
198
+ }
199
+
200
+ if (url.path != null && typeof url.path !== 'string') {
201
+ throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.')
202
+ }
203
+
204
+ if (url.pathname != null && typeof url.pathname !== 'string') {
205
+ throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.')
206
+ }
207
+
208
+ if (url.hostname != null && typeof url.hostname !== 'string') {
209
+ throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.')
210
+ }
211
+
212
+ if (url.origin != null && typeof url.origin !== 'string') {
213
+ throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.')
214
+ }
215
+
216
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
217
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
218
+ }
219
+
220
+ const port = url.port != null
221
+ ? url.port
222
+ : (url.protocol === 'https:' ? 443 : 80)
223
+ let origin = url.origin != null
224
+ ? url.origin
225
+ : `${url.protocol || ''}//${url.hostname || ''}:${port}`
226
+ let path = url.path != null
227
+ ? url.path
228
+ : `${url.pathname || ''}${url.search || ''}`
229
+
230
+ if (origin[origin.length - 1] === '/') {
231
+ origin = origin.slice(0, origin.length - 1)
232
+ }
233
+
234
+ if (path && path[0] !== '/') {
235
+ path = `/${path}`
236
+ }
237
+ // new URL(path, origin) is unsafe when `path` contains an absolute URL
238
+ // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL:
239
+ // If first parameter is a relative URL, second param is required, and will be used as the base URL.
240
+ // If first parameter is an absolute URL, a given second param will be ignored.
241
+ return new URL(`${origin}${path}`)
242
+ }
243
+
244
+ if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
245
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
246
+ }
247
+
248
+ return url
249
+ }
250
+
251
+ /**
252
+ * @param {string|URL|Record<string, string>} url
253
+ * @returns {URL}
254
+ */
255
+ function parseOrigin (url) {
256
+ url = parseURL(url)
257
+
258
+ if (url.pathname !== '/' || url.search || url.hash) {
259
+ throw new InvalidArgumentError('invalid url')
260
+ }
261
+
262
+ return url
263
+ }
264
+
265
+ /**
266
+ * @param {string} host
267
+ * @returns {string}
268
+ */
269
+ function getHostname (host) {
270
+ if (host[0] === '[') {
271
+ const idx = host.indexOf(']')
272
+
273
+ assert(idx !== -1)
274
+ return host.substring(1, idx)
275
+ }
276
+
277
+ const idx = host.indexOf(':')
278
+ if (idx === -1) return host
279
+
280
+ return host.substring(0, idx)
281
+ }
282
+
283
+ /**
284
+ * IP addresses are not valid server names per RFC6066
285
+ * Currently, the only server names supported are DNS hostnames
286
+ * @param {string|null} host
287
+ * @returns {string|null}
288
+ */
289
+ function getServerName (host) {
290
+ if (!host) {
291
+ return null
292
+ }
293
+
294
+ assert(typeof host === 'string')
295
+
296
+ const servername = getHostname(host)
297
+ if (net.isIP(servername)) {
298
+ return ''
299
+ }
300
+
301
+ return servername
302
+ }
303
+
304
+ /**
305
+ * @function
306
+ * @template T
307
+ * @param {T} obj
308
+ * @returns {T}
309
+ */
310
+ function deepClone (obj) {
311
+ return JSON.parse(JSON.stringify(obj))
312
+ }
313
+
314
+ /**
315
+ * @param {*} obj
316
+ * @returns {obj is AsyncIterable}
317
+ */
318
+ function isAsyncIterable (obj) {
319
+ return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
320
+ }
321
+
322
+ /**
323
+ * @param {*} obj
324
+ * @returns {obj is Iterable}
325
+ */
326
+ function isIterable (obj) {
327
+ return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))
328
+ }
329
+
330
+ /**
331
+ * Checks whether an object has a safe Symbol.iterator — i.e. one that is
332
+ * either own or inherited from a non-Object.prototype chain. This prevents
333
+ * prototype-pollution attacks from injecting a fake iterator on
334
+ * Object.prototype.
335
+ * @param {object} obj
336
+ * @returns {boolean}
337
+ */
338
+ function hasSafeIterator (obj) {
339
+ const prototype = Object.getPrototypeOf(obj)
340
+ const ownIterator = Object.prototype.hasOwnProperty.call(obj, Symbol.iterator)
341
+ return ownIterator || (prototype != null && prototype !== Object.prototype && typeof obj[Symbol.iterator] === 'function')
342
+ }
343
+
344
+ /**
345
+ * @param {Blob|Buffer|import ('stream').Stream} body
346
+ * @returns {number|null}
347
+ */
348
+ function bodyLength (body) {
349
+ if (body == null) {
350
+ return 0
351
+ } else if (isStream(body)) {
352
+ const state = body._readableState
353
+ return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length)
354
+ ? state.length
355
+ : null
356
+ } else if (isBlobLike(body)) {
357
+ return body.size != null ? body.size : null
358
+ } else if (isBuffer(body)) {
359
+ return body.byteLength
360
+ }
361
+
362
+ return null
363
+ }
364
+
365
+ /**
366
+ * @param {import ('stream').Stream} body
367
+ * @returns {boolean}
368
+ */
369
+ function isDestroyed (body) {
370
+ return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
371
+ }
372
+
373
+ /**
374
+ * @param {import ('stream').Stream} stream
375
+ * @param {Error} [err]
376
+ * @returns {void}
377
+ */
378
+ function destroy (stream, err) {
379
+ if (stream == null || !isStream(stream) || isDestroyed(stream)) {
380
+ return
381
+ }
382
+
383
+ if (typeof stream.destroy === 'function') {
384
+ if (Object.getPrototypeOf(stream).constructor === IncomingMessage) {
385
+ // See: https://github.com/nodejs/node/pull/38505/files
386
+ stream.socket = null
387
+ }
388
+
389
+ stream.destroy(err)
390
+ } else if (err) {
391
+ queueMicrotask(() => {
392
+ stream.emit('error', err)
393
+ })
394
+ }
395
+
396
+ if (stream.destroyed !== true) {
397
+ stream[kDestroyed] = true
398
+ }
399
+ }
400
+
401
+ const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
402
+ /**
403
+ * @param {string} val
404
+ * @returns {number | null}
405
+ */
406
+ function parseKeepAliveTimeout (val) {
407
+ const m = val.match(KEEPALIVE_TIMEOUT_EXPR)
408
+ return m ? parseInt(m[1], 10) * 1000 : null
409
+ }
410
+
411
+ /**
412
+ * Retrieves a header name and returns its lowercase value.
413
+ * @param {string | Buffer} value Header name
414
+ * @returns {string}
415
+ */
416
+ function headerNameToString (value) {
417
+ return typeof value === 'string'
418
+ ? headerNameLowerCasedRecord[value] ?? value.toLowerCase()
419
+ : tree.lookup(value) ?? value.toString('latin1').toLowerCase()
420
+ }
421
+
422
+ /**
423
+ * Receive the buffer as a string and return its lowercase value.
424
+ * @param {Buffer} value Header name
425
+ * @returns {string}
426
+ */
427
+ function bufferToLowerCasedHeaderName (value) {
428
+ return tree.lookup(value) ?? value.toString('latin1').toLowerCase()
429
+ }
430
+
431
+ /**
432
+ * @param {(Buffer | string)[]} headers
433
+ * @param {Record<string, string | string[]>} [obj]
434
+ * @returns {Record<string, string | string[]>}
435
+ */
436
+ function parseHeaders (headers, obj) {
437
+ if (obj === undefined) obj = {}
438
+
439
+ for (let i = 0; i < headers.length; i += 2) {
440
+ const key = headerNameToString(headers[i])
441
+ let val = obj[key]
442
+
443
+ if (val) {
444
+ if (typeof val === 'string') {
445
+ val = [val]
446
+ obj[key] = val
447
+ }
448
+ val.push(headers[i + 1].toString('latin1'))
449
+ } else {
450
+ const headersValue = headers[i + 1]
451
+ if (typeof headersValue === 'string') {
452
+ obj[key] = headersValue
453
+ } else {
454
+ obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('latin1')) : headersValue.toString('latin1')
455
+ }
456
+ }
457
+ }
458
+
459
+ return obj
460
+ }
461
+
462
+ /**
463
+ * @param {Buffer[]} headers
464
+ * @returns {string[]}
465
+ */
466
+ function parseRawHeaders (headers) {
467
+ const headersLength = headers.length
468
+ /**
469
+ * @type {string[]}
470
+ */
471
+ const ret = new Array(headersLength)
472
+
473
+ let key
474
+ let val
475
+
476
+ for (let n = 0; n < headersLength; n += 2) {
477
+ key = headers[n]
478
+ val = headers[n + 1]
479
+
480
+ typeof key !== 'string' && (key = key.toString())
481
+ typeof val !== 'string' && (val = val.toString('latin1'))
482
+
483
+ ret[n] = key
484
+ ret[n + 1] = val
485
+ }
486
+
487
+ return ret
488
+ }
489
+
490
+ /**
491
+ * @param {string[]} headers
492
+ * @param {Buffer[]} headers
493
+ */
494
+ function encodeRawHeaders (headers) {
495
+ if (!Array.isArray(headers)) {
496
+ throw new TypeError('expected headers to be an array')
497
+ }
498
+ return headers.map(x => Buffer.from(x))
499
+ }
500
+
501
+ /**
502
+ * @param {*} buffer
503
+ * @returns {buffer is Buffer}
504
+ */
505
+ function isBuffer (buffer) {
506
+ // See, https://github.com/mcollina/undici/pull/319
507
+ return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
508
+ }
509
+
510
+ /**
511
+ * Asserts that the handler object is a request handler.
512
+ *
513
+ * @param {object} handler
514
+ * @param {string} method
515
+ * @param {string} [upgrade]
516
+ * @returns {asserts handler is import('../api/api-request').RequestHandler}
517
+ */
518
+ function assertRequestHandler (handler, method, upgrade) {
519
+ if (!handler || typeof handler !== 'object') {
520
+ throw new InvalidArgumentError('handler must be an object')
521
+ }
522
+
523
+ if (typeof handler.onRequestStart === 'function') {
524
+ // TODO (fix): More checks...
525
+ return
526
+ }
527
+
528
+ if (typeof handler.onConnect !== 'function') {
529
+ throw new InvalidArgumentError('invalid onConnect method')
530
+ }
531
+
532
+ if (typeof handler.onError !== 'function') {
533
+ throw new InvalidArgumentError('invalid onError method')
534
+ }
535
+
536
+ if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) {
537
+ throw new InvalidArgumentError('invalid onBodySent method')
538
+ }
539
+
540
+ if (upgrade || method === 'CONNECT') {
541
+ if (typeof handler.onUpgrade !== 'function') {
542
+ throw new InvalidArgumentError('invalid onUpgrade method')
543
+ }
544
+ } else {
545
+ if (typeof handler.onHeaders !== 'function') {
546
+ throw new InvalidArgumentError('invalid onHeaders method')
547
+ }
548
+
549
+ if (typeof handler.onData !== 'function') {
550
+ throw new InvalidArgumentError('invalid onData method')
551
+ }
552
+
553
+ if (typeof handler.onComplete !== 'function') {
554
+ throw new InvalidArgumentError('invalid onComplete method')
555
+ }
556
+ }
557
+ }
558
+
559
+ /**
560
+ * A body is disturbed if it has been read from and it cannot be re-used without
561
+ * losing state or data.
562
+ * @param {import('node:stream').Readable} body
563
+ * @returns {boolean}
564
+ */
565
+ function isDisturbed (body) {
566
+ // TODO (fix): Why is body[kBodyUsed] needed?
567
+ return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
568
+ }
569
+
570
+ /**
571
+ * @typedef {object} SocketInfo
572
+ * @property {string} [localAddress]
573
+ * @property {number} [localPort]
574
+ * @property {string} [remoteAddress]
575
+ * @property {number} [remotePort]
576
+ * @property {string} [remoteFamily]
577
+ * @property {number} [timeout]
578
+ * @property {number} bytesWritten
579
+ * @property {number} bytesRead
580
+ */
581
+
582
+ /**
583
+ * @param {import('net').Socket} socket
584
+ * @returns {SocketInfo}
585
+ */
586
+ function getSocketInfo (socket) {
587
+ return {
588
+ localAddress: socket.localAddress,
589
+ localPort: socket.localPort,
590
+ remoteAddress: socket.remoteAddress,
591
+ remotePort: socket.remotePort,
592
+ remoteFamily: socket.remoteFamily,
593
+ timeout: socket.timeout,
594
+ bytesWritten: socket.bytesWritten,
595
+ bytesRead: socket.bytesRead
596
+ }
597
+ }
598
+
599
+ /**
600
+ * @param {Iterable} iterable
601
+ * @returns {ReadableStream}
602
+ */
603
+ function ReadableStreamFrom (iterable) {
604
+ // We cannot use ReadableStream.from here because it does not return a byte stream.
605
+
606
+ let iterator
607
+ return new ReadableStream(
608
+ {
609
+ start () {
610
+ iterator = iterable[Symbol.asyncIterator]()
611
+ },
612
+ pull (controller) {
613
+ return iterator.next().then(({ done, value }) => {
614
+ if (done) {
615
+ return queueMicrotask(() => {
616
+ controller.close()
617
+ controller.byobRequest?.respond(0)
618
+ })
619
+ } else {
620
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
621
+ if (buf.byteLength) {
622
+ return controller.enqueue(new Uint8Array(buf))
623
+ } else {
624
+ return this.pull(controller)
625
+ }
626
+ }
627
+ })
628
+ },
629
+ cancel () {
630
+ return iterator.return()
631
+ },
632
+ type: 'bytes'
633
+ }
634
+ )
635
+ }
636
+
637
+ /**
638
+ * The object should be a FormData instance and contains all the required
639
+ * methods.
640
+ * @param {*} object
641
+ * @returns {object is FormData}
642
+ */
643
+ function isFormDataLike (object) {
644
+ return (
645
+ object &&
646
+ typeof object === 'object' &&
647
+ typeof object.append === 'function' &&
648
+ typeof object.delete === 'function' &&
649
+ typeof object.get === 'function' &&
650
+ typeof object.getAll === 'function' &&
651
+ typeof object.has === 'function' &&
652
+ typeof object.set === 'function' &&
653
+ object[Symbol.toStringTag] === 'FormData'
654
+ )
655
+ }
656
+
657
+ function addAbortListener (signal, listener) {
658
+ if ('addEventListener' in signal) {
659
+ signal.addEventListener('abort', listener, { once: true })
660
+ return () => signal.removeEventListener('abort', listener)
661
+ }
662
+ signal.once('abort', listener)
663
+ return () => signal.removeListener('abort', listener)
664
+ }
665
+
666
+ const validTokenChars = new Uint8Array([
667
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
668
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
669
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
670
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
671
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
672
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
673
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
674
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
675
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
676
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
677
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
678
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
679
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
680
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
681
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
682
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240-255
683
+ ])
684
+
685
+ /**
686
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
687
+ * @param {number} c
688
+ * @returns {boolean}
689
+ */
690
+ function isTokenCharCode (c) {
691
+ return (validTokenChars[c] === 1)
692
+ }
693
+
694
+ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
695
+
696
+ /**
697
+ * @param {string} characters
698
+ * @returns {boolean}
699
+ */
700
+ function isValidHTTPToken (characters) {
701
+ if (characters.length >= 12) return tokenRegExp.test(characters)
702
+ if (characters.length === 0) return false
703
+
704
+ for (let i = 0; i < characters.length; i++) {
705
+ if (validTokenChars[characters.charCodeAt(i)] !== 1) {
706
+ return false
707
+ }
708
+ }
709
+ return true
710
+ }
711
+
712
+ // headerCharRegex have been lifted from
713
+ // https://github.com/nodejs/node/blob/main/lib/_http_common.js
714
+
715
+ /**
716
+ * Matches if val contains an invalid field-vchar
717
+ * field-value = *( field-content / obs-fold )
718
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
719
+ * field-vchar = VCHAR / obs-text
720
+ */
721
+ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
722
+
723
+ /**
724
+ * @param {string} characters
725
+ * @returns {boolean}
726
+ */
727
+ function isValidHeaderValue (characters) {
728
+ return !headerCharRegex.test(characters)
729
+ }
730
+
731
+ const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/
732
+
733
+ /**
734
+ * @typedef {object} RangeHeader
735
+ * @property {number} start
736
+ * @property {number | null} end
737
+ * @property {number | null} size
738
+ */
739
+
740
+ /**
741
+ * Parse accordingly to RFC 9110
742
+ * @see https://www.rfc-editor.org/rfc/rfc9110#field.content-range
743
+ * @param {string} [range]
744
+ * @returns {RangeHeader|null}
745
+ */
746
+ function parseRangeHeader (range) {
747
+ if (range == null || range === '') return { start: 0, end: null, size: null }
748
+
749
+ const m = range ? range.match(rangeHeaderRegex) : null
750
+ return m
751
+ ? {
752
+ start: parseInt(m[1]),
753
+ end: m[2] ? parseInt(m[2]) : null,
754
+ size: m[3] ? parseInt(m[3]) : null
755
+ }
756
+ : null
757
+ }
758
+
759
+ /**
760
+ * @template {import("events").EventEmitter} T
761
+ * @param {T} obj
762
+ * @param {string} name
763
+ * @param {(...args: any[]) => void} listener
764
+ * @returns {T}
765
+ */
766
+ function addListener (obj, name, listener) {
767
+ const listeners = (obj[kListeners] ??= [])
768
+ listeners.push([name, listener])
769
+ obj.on(name, listener)
770
+ return obj
771
+ }
772
+
773
+ /**
774
+ * @template {import("events").EventEmitter} T
775
+ * @param {T} obj
776
+ * @returns {T}
777
+ */
778
+ function removeAllListeners (obj) {
779
+ if (obj[kListeners] != null) {
780
+ for (const [name, listener] of obj[kListeners]) {
781
+ obj.removeListener(name, listener)
782
+ }
783
+ obj[kListeners] = null
784
+ }
785
+ return obj
786
+ }
787
+
788
+ /**
789
+ * @param {import ('../dispatcher/client')} client
790
+ * @param {import ('../core/request')} request
791
+ * @param {Error} err
792
+ */
793
+ function errorRequest (client, request, err) {
794
+ try {
795
+ request.onError(err)
796
+ assert(request.aborted)
797
+ } catch (err) {
798
+ client.emit('error', err)
799
+ }
800
+ }
801
+
802
+ /**
803
+ * @param {WeakRef<net.Socket>} socketWeakRef
804
+ * @param {object} opts
805
+ * @param {number} opts.timeout
806
+ * @param {string} opts.hostname
807
+ * @param {number} opts.port
808
+ * @returns {() => void}
809
+ */
810
+ const setupConnectTimeout = process.platform === 'win32'
811
+ ? (socketWeakRef, opts) => {
812
+ if (!opts.timeout) {
813
+ return noop
814
+ }
815
+
816
+ let s1 = null
817
+ let s2 = null
818
+ const fastTimer = timers.setFastTimeout(() => {
819
+ // setImmediate is added to make sure that we prioritize socket error events over timeouts
820
+ s1 = setImmediate(() => {
821
+ // Windows needs an extra setImmediate probably due to implementation differences in the socket logic
822
+ s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts))
823
+ })
824
+ }, opts.timeout)
825
+ return () => {
826
+ timers.clearFastTimeout(fastTimer)
827
+ clearImmediate(s1)
828
+ clearImmediate(s2)
829
+ }
830
+ }
831
+ : (socketWeakRef, opts) => {
832
+ if (!opts.timeout) {
833
+ return noop
834
+ }
835
+
836
+ let s1 = null
837
+ const fastTimer = timers.setFastTimeout(() => {
838
+ // setImmediate is added to make sure that we prioritize socket error events over timeouts
839
+ s1 = setImmediate(() => {
840
+ onConnectTimeout(socketWeakRef.deref(), opts)
841
+ })
842
+ }, opts.timeout)
843
+ return () => {
844
+ timers.clearFastTimeout(fastTimer)
845
+ clearImmediate(s1)
846
+ }
847
+ }
848
+
849
+ /**
850
+ * @param {net.Socket} socket
851
+ * @param {object} opts
852
+ * @param {number} opts.timeout
853
+ * @param {string} opts.hostname
854
+ * @param {number} opts.port
855
+ */
856
+ function onConnectTimeout (socket, opts) {
857
+ // The socket could be already garbage collected
858
+ if (socket == null) {
859
+ return
860
+ }
861
+
862
+ let message = 'Connect Timeout Error'
863
+ if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
864
+ message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
865
+ } else {
866
+ message += ` (attempted address: ${opts.hostname}:${opts.port},`
867
+ }
868
+
869
+ message += ` timeout: ${opts.timeout}ms)`
870
+
871
+ destroy(socket, new ConnectTimeoutError(message))
872
+ }
873
+
874
+ /**
875
+ * @param {string} urlString
876
+ * @returns {string}
877
+ */
878
+ function getProtocolFromUrlString (urlString) {
879
+ if (
880
+ urlString[0] === 'h' &&
881
+ urlString[1] === 't' &&
882
+ urlString[2] === 't' &&
883
+ urlString[3] === 'p'
884
+ ) {
885
+ switch (urlString[4]) {
886
+ case ':':
887
+ return 'http:'
888
+ case 's':
889
+ if (urlString[5] === ':') {
890
+ return 'https:'
891
+ }
892
+ }
893
+ }
894
+ // fallback if none of the usual suspects
895
+ return urlString.slice(0, urlString.indexOf(':') + 1)
896
+ }
897
+
898
+ const kEnumerableProperty = Object.create(null)
899
+ kEnumerableProperty.enumerable = true
900
+
901
+ const normalizedMethodRecordsBase = {
902
+ delete: 'DELETE',
903
+ DELETE: 'DELETE',
904
+ get: 'GET',
905
+ GET: 'GET',
906
+ head: 'HEAD',
907
+ HEAD: 'HEAD',
908
+ options: 'OPTIONS',
909
+ OPTIONS: 'OPTIONS',
910
+ post: 'POST',
911
+ POST: 'POST',
912
+ put: 'PUT',
913
+ PUT: 'PUT'
914
+ }
915
+
916
+ const normalizedMethodRecords = {
917
+ ...normalizedMethodRecordsBase,
918
+ patch: 'patch',
919
+ PATCH: 'PATCH'
920
+ }
921
+
922
+ // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
923
+ Object.setPrototypeOf(normalizedMethodRecordsBase, null)
924
+ Object.setPrototypeOf(normalizedMethodRecords, null)
925
+
926
+ module.exports = {
927
+ kEnumerableProperty,
928
+ isDisturbed,
929
+ isBlobLike,
930
+ parseOrigin,
931
+ parseURL,
932
+ getServerName,
933
+ isStream,
934
+ isIterable,
935
+ hasSafeIterator,
936
+ isAsyncIterable,
937
+ isDestroyed,
938
+ headerNameToString,
939
+ bufferToLowerCasedHeaderName,
940
+ addListener,
941
+ removeAllListeners,
942
+ errorRequest,
943
+ parseRawHeaders,
944
+ encodeRawHeaders,
945
+ parseHeaders,
946
+ parseKeepAliveTimeout,
947
+ destroy,
948
+ bodyLength,
949
+ deepClone,
950
+ ReadableStreamFrom,
951
+ isBuffer,
952
+ assertRequestHandler,
953
+ getSocketInfo,
954
+ isFormDataLike,
955
+ pathHasQueryOrFragment,
956
+ serializePathWithQuery,
957
+ addAbortListener,
958
+ isValidHTTPToken,
959
+ isValidHeaderValue,
960
+ isTokenCharCode,
961
+ parseRangeHeader,
962
+ normalizedMethodRecordsBase,
963
+ normalizedMethodRecords,
964
+ isValidPort,
965
+ isHttpOrHttpsPrefixed,
966
+ nodeMajor,
967
+ nodeMinor,
968
+ safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
969
+ wrapRequestBody,
970
+ setupConnectTimeout,
971
+ getProtocolFromUrlString
972
+ }