util.js 24 KB

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