UNPKG

get-it

Version:

Generic HTTP request library for node, browsers and workers

1 lines 82.3 kB
{"version":3,"file":"index.cjs","sources":["../src/util/middlewareReducer.ts","../src/createRequester.ts","../src/util/pubsub.ts","../node_modules/follow-redirects/index.js","../node_modules/follow-redirects/debug.js","../src/util/lowerCaseHeaders.ts","../src/request/node/proxy.ts","../src/request/node/tunnel.ts","../src/request/node-request.ts","../src/request/node/simpleConcat.ts","../src/request/node/timedOut.ts","../src/index.ts"],"sourcesContent":["import type {ApplyMiddleware, MiddlewareReducer} from 'get-it'\n\nexport const middlewareReducer = (middleware: MiddlewareReducer) =>\n function applyMiddleware(hook, defaultValue, ...args) {\n const bailEarly = hook === 'onError'\n\n let value = defaultValue\n for (let i = 0; i < middleware[hook].length; i++) {\n const handler = middleware[hook][i]\n // @ts-expect-error -- find a better way to deal with argument tuples\n value = handler(value, ...args)\n\n if (bailEarly && !value) {\n break\n }\n }\n\n return value\n } as ApplyMiddleware\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {processOptions} from './middleware/defaultOptionsProcessor'\nimport {validateOptions} from './middleware/defaultOptionsValidator'\nimport type {\n HttpContext,\n HttpRequest,\n HttpRequestOngoing,\n Middleware,\n MiddlewareChannels,\n MiddlewareHooks,\n MiddlewareReducer,\n MiddlewareResponse,\n Middlewares,\n Requester,\n RequestOptions,\n} from './types'\nimport {middlewareReducer} from './util/middlewareReducer'\nimport {createPubSub} from './util/pubsub'\n\nconst channelNames = [\n 'request',\n 'response',\n 'progress',\n 'error',\n 'abort',\n] satisfies (keyof MiddlewareChannels)[]\nconst middlehooks = [\n 'processOptions',\n 'validateOptions',\n 'interceptRequest',\n 'finalizeOptions',\n 'onRequest',\n 'onResponse',\n 'onError',\n 'onReturn',\n 'onHeaders',\n] satisfies (keyof MiddlewareHooks)[]\n\n/** @public */\nexport function createRequester(initMiddleware: Middlewares, httpRequest: HttpRequest): Requester {\n const loadedMiddleware: Middlewares = []\n const middleware: MiddlewareReducer = middlehooks.reduce(\n (ware, name) => {\n ware[name] = ware[name] || []\n return ware\n },\n {\n processOptions: [processOptions],\n validateOptions: [validateOptions],\n } as any,\n )\n\n function request(opts: RequestOptions | string) {\n const onResponse = (reqErr: Error | null, res: MiddlewareResponse, ctx: HttpContext) => {\n let error = reqErr\n let response: MiddlewareResponse | null = res\n\n // We're processing non-errors first, in case a middleware converts the\n // response into an error (for instance, status >= 400 == HttpError)\n if (!error) {\n try {\n response = applyMiddleware('onResponse', res, ctx)\n } catch (err: any) {\n response = null\n error = err\n }\n }\n\n // Apply error middleware - if middleware return the same (or a different) error,\n // publish as an error event. If we *don't* return an error, assume it has been handled\n error = error && applyMiddleware('onError', error, ctx)\n\n // Figure out if we should publish on error/response channels\n if (error) {\n channels.error.publish(error)\n } else if (response) {\n channels.response.publish(response)\n }\n }\n\n const channels: MiddlewareChannels = channelNames.reduce((target, name) => {\n target[name] = createPubSub() as MiddlewareChannels[typeof name]\n return target\n }, {} as any)\n\n // Prepare a middleware reducer that can be reused throughout the lifecycle\n const applyMiddleware = middlewareReducer(middleware)\n\n // Parse the passed options\n const options = applyMiddleware('processOptions', opts as RequestOptions)\n\n // Validate the options\n applyMiddleware('validateOptions', options)\n\n // Build a context object we can pass to child handlers\n const context = {options, channels, applyMiddleware}\n\n // We need to hold a reference to the current, ongoing request,\n // in order to allow cancellation. In the case of the retry middleware,\n // a new request might be triggered\n let ongoingRequest: HttpRequestOngoing | undefined\n const unsubscribe = channels.request.subscribe((ctx) => {\n // Let request adapters (node/browser) perform the actual request\n ongoingRequest = httpRequest(ctx, (err, res) => onResponse(err, res!, ctx))\n })\n\n // If we abort the request, prevent further requests from happening,\n // and be sure to cancel any ongoing request (obviously)\n channels.abort.subscribe(() => {\n unsubscribe()\n if (ongoingRequest) {\n ongoingRequest.abort()\n }\n })\n\n // See if any middleware wants to modify the return value - for instance\n // the promise or observable middlewares\n const returnValue = applyMiddleware('onReturn', channels, context)\n\n // If return value has been modified by a middleware, we expect the middleware\n // to publish on the 'request' channel. If it hasn't been modified, we want to\n // trigger it right away\n if (returnValue === channels) {\n channels.request.publish(context)\n }\n\n return returnValue\n }\n\n request.use = function use(newMiddleware: Middleware) {\n if (!newMiddleware) {\n throw new Error('Tried to add middleware that resolved to falsey value')\n }\n\n if (typeof newMiddleware === 'function') {\n throw new Error(\n 'Tried to add middleware that was a function. It probably expects you to pass options to it.',\n )\n }\n\n if (newMiddleware.onReturn && middleware.onReturn.length > 0) {\n throw new Error(\n 'Tried to add new middleware with `onReturn` handler, but another handler has already been registered for this event',\n )\n }\n\n middlehooks.forEach((key) => {\n if (newMiddleware[key]) {\n middleware[key].push(newMiddleware[key] as any)\n }\n })\n\n loadedMiddleware.push(newMiddleware)\n return request\n }\n\n request.clone = () => createRequester(loadedMiddleware, httpRequest)\n\n initMiddleware.forEach(request.use)\n\n return request\n}\n","// Code borrowed from https://github.com/bjoerge/nano-pubsub\n\nimport type {PubSub, Subscriber} from 'get-it'\n\nexport function createPubSub<Message = void>(): PubSub<Message> {\n const subscribers: {[id: string]: Subscriber<Message>} = Object.create(null)\n let nextId = 0\n function subscribe(subscriber: Subscriber<Message>) {\n const id = nextId++\n subscribers[id] = subscriber\n return function unsubscribe() {\n delete subscribers[id]\n }\n }\n\n function publish(event: Message) {\n for (const id in subscribers) {\n subscribers[id](event)\n }\n }\n\n return {\n publish,\n subscribe,\n }\n}\n","var url = require(\"url\");\nvar URL = url.URL;\nvar http = require(\"http\");\nvar https = require(\"https\");\nvar Writable = require(\"stream\").Writable;\nvar assert = require(\"assert\");\nvar debug = require(\"./debug\");\n\n// Preventive platform detection\n// istanbul ignore next\n(function detectUnsupportedEnvironment() {\n var looksLikeNode = typeof process !== \"undefined\";\n var looksLikeBrowser = typeof window !== \"undefined\" && typeof document !== \"undefined\";\n var looksLikeV8 = isFunction(Error.captureStackTrace);\n if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) {\n console.warn(\"The follow-redirects package should be excluded from browser builds.\");\n }\n}());\n\n// Whether to use the native URL object or the legacy url module\nvar useNativeURL = false;\ntry {\n assert(new URL(\"\"));\n}\ncatch (error) {\n useNativeURL = error.code === \"ERR_INVALID_URL\";\n}\n\n// URL fields to preserve in copy operations\nvar preservedUrlFields = [\n \"auth\",\n \"host\",\n \"hostname\",\n \"href\",\n \"path\",\n \"pathname\",\n \"port\",\n \"protocol\",\n \"query\",\n \"search\",\n \"hash\",\n];\n\n// Create handlers that pass events from native requests\nvar events = [\"abort\", \"aborted\", \"connect\", \"error\", \"socket\", \"timeout\"];\nvar eventHandlers = Object.create(null);\nevents.forEach(function (event) {\n eventHandlers[event] = function (arg1, arg2, arg3) {\n this._redirectable.emit(event, arg1, arg2, arg3);\n };\n});\n\n// Error types with codes\nvar InvalidUrlError = createErrorType(\n \"ERR_INVALID_URL\",\n \"Invalid URL\",\n TypeError\n);\nvar RedirectionError = createErrorType(\n \"ERR_FR_REDIRECTION_FAILURE\",\n \"Redirected request failed\"\n);\nvar TooManyRedirectsError = createErrorType(\n \"ERR_FR_TOO_MANY_REDIRECTS\",\n \"Maximum number of redirects exceeded\",\n RedirectionError\n);\nvar MaxBodyLengthExceededError = createErrorType(\n \"ERR_FR_MAX_BODY_LENGTH_EXCEEDED\",\n \"Request body larger than maxBodyLength limit\"\n);\nvar WriteAfterEndError = createErrorType(\n \"ERR_STREAM_WRITE_AFTER_END\",\n \"write after end\"\n);\n\n// istanbul ignore next\nvar destroy = Writable.prototype.destroy || noop;\n\n// An HTTP(S) request that can be redirected\nfunction RedirectableRequest(options, responseCallback) {\n // Initialize the request\n Writable.call(this);\n this._sanitizeOptions(options);\n this._options = options;\n this._ended = false;\n this._ending = false;\n this._redirectCount = 0;\n this._redirects = [];\n this._requestBodyLength = 0;\n this._requestBodyBuffers = [];\n\n // Attach a callback if passed\n if (responseCallback) {\n this.on(\"response\", responseCallback);\n }\n\n // React to responses of native requests\n var self = this;\n this._onNativeResponse = function (response) {\n try {\n self._processResponse(response);\n }\n catch (cause) {\n self.emit(\"error\", cause instanceof RedirectionError ?\n cause : new RedirectionError({ cause: cause }));\n }\n };\n\n // Perform the first request\n this._performRequest();\n}\nRedirectableRequest.prototype = Object.create(Writable.prototype);\n\nRedirectableRequest.prototype.abort = function () {\n destroyRequest(this._currentRequest);\n this._currentRequest.abort();\n this.emit(\"abort\");\n};\n\nRedirectableRequest.prototype.destroy = function (error) {\n destroyRequest(this._currentRequest, error);\n destroy.call(this, error);\n return this;\n};\n\n// Writes buffered data to the current native request\nRedirectableRequest.prototype.write = function (data, encoding, callback) {\n // Writing is not allowed if end has been called\n if (this._ending) {\n throw new WriteAfterEndError();\n }\n\n // Validate input and shift parameters if necessary\n if (!isString(data) && !isBuffer(data)) {\n throw new TypeError(\"data should be a string, Buffer or Uint8Array\");\n }\n if (isFunction(encoding)) {\n callback = encoding;\n encoding = null;\n }\n\n // Ignore empty buffers, since writing them doesn't invoke the callback\n // https://github.com/nodejs/node/issues/22066\n if (data.length === 0) {\n if (callback) {\n callback();\n }\n return;\n }\n // Only write when we don't exceed the maximum body length\n if (this._requestBodyLength + data.length <= this._options.maxBodyLength) {\n this._requestBodyLength += data.length;\n this._requestBodyBuffers.push({ data: data, encoding: encoding });\n this._currentRequest.write(data, encoding, callback);\n }\n // Error when we exceed the maximum body length\n else {\n this.emit(\"error\", new MaxBodyLengthExceededError());\n this.abort();\n }\n};\n\n// Ends the current native request\nRedirectableRequest.prototype.end = function (data, encoding, callback) {\n // Shift parameters if necessary\n if (isFunction(data)) {\n callback = data;\n data = encoding = null;\n }\n else if (isFunction(encoding)) {\n callback = encoding;\n encoding = null;\n }\n\n // Write data if needed and end\n if (!data) {\n this._ended = this._ending = true;\n this._currentRequest.end(null, null, callback);\n }\n else {\n var self = this;\n var currentRequest = this._currentRequest;\n this.write(data, encoding, function () {\n self._ended = true;\n currentRequest.end(null, null, callback);\n });\n this._ending = true;\n }\n};\n\n// Sets a header value on the current native request\nRedirectableRequest.prototype.setHeader = function (name, value) {\n this._options.headers[name] = value;\n this._currentRequest.setHeader(name, value);\n};\n\n// Clears a header value on the current native request\nRedirectableRequest.prototype.removeHeader = function (name) {\n delete this._options.headers[name];\n this._currentRequest.removeHeader(name);\n};\n\n// Global timeout for all underlying requests\nRedirectableRequest.prototype.setTimeout = function (msecs, callback) {\n var self = this;\n\n // Destroys the socket on timeout\n function destroyOnTimeout(socket) {\n socket.setTimeout(msecs);\n socket.removeListener(\"timeout\", socket.destroy);\n socket.addListener(\"timeout\", socket.destroy);\n }\n\n // Sets up a timer to trigger a timeout event\n function startTimer(socket) {\n if (self._timeout) {\n clearTimeout(self._timeout);\n }\n self._timeout = setTimeout(function () {\n self.emit(\"timeout\");\n clearTimer();\n }, msecs);\n destroyOnTimeout(socket);\n }\n\n // Stops a timeout from triggering\n function clearTimer() {\n // Clear the timeout\n if (self._timeout) {\n clearTimeout(self._timeout);\n self._timeout = null;\n }\n\n // Clean up all attached listeners\n self.removeListener(\"abort\", clearTimer);\n self.removeListener(\"error\", clearTimer);\n self.removeListener(\"response\", clearTimer);\n self.removeListener(\"close\", clearTimer);\n if (callback) {\n self.removeListener(\"timeout\", callback);\n }\n if (!self.socket) {\n self._currentRequest.removeListener(\"socket\", startTimer);\n }\n }\n\n // Attach callback if passed\n if (callback) {\n this.on(\"timeout\", callback);\n }\n\n // Start the timer if or when the socket is opened\n if (this.socket) {\n startTimer(this.socket);\n }\n else {\n this._currentRequest.once(\"socket\", startTimer);\n }\n\n // Clean up on events\n this.on(\"socket\", destroyOnTimeout);\n this.on(\"abort\", clearTimer);\n this.on(\"error\", clearTimer);\n this.on(\"response\", clearTimer);\n this.on(\"close\", clearTimer);\n\n return this;\n};\n\n// Proxy all other public ClientRequest methods\n[\n \"flushHeaders\", \"getHeader\",\n \"setNoDelay\", \"setSocketKeepAlive\",\n].forEach(function (method) {\n RedirectableRequest.prototype[method] = function (a, b) {\n return this._currentRequest[method](a, b);\n };\n});\n\n// Proxy all public ClientRequest properties\n[\"aborted\", \"connection\", \"socket\"].forEach(function (property) {\n Object.defineProperty(RedirectableRequest.prototype, property, {\n get: function () { return this._currentRequest[property]; },\n });\n});\n\nRedirectableRequest.prototype._sanitizeOptions = function (options) {\n // Ensure headers are always present\n if (!options.headers) {\n options.headers = {};\n }\n\n // Since http.request treats host as an alias of hostname,\n // but the url module interprets host as hostname plus port,\n // eliminate the host property to avoid confusion.\n if (options.host) {\n // Use hostname if set, because it has precedence\n if (!options.hostname) {\n options.hostname = options.host;\n }\n delete options.host;\n }\n\n // Complete the URL object when necessary\n if (!options.pathname && options.path) {\n var searchPos = options.path.indexOf(\"?\");\n if (searchPos < 0) {\n options.pathname = options.path;\n }\n else {\n options.pathname = options.path.substring(0, searchPos);\n options.search = options.path.substring(searchPos);\n }\n }\n};\n\n\n// Executes the next native request (initial or redirect)\nRedirectableRequest.prototype._performRequest = function () {\n // Load the native protocol\n var protocol = this._options.protocol;\n var nativeProtocol = this._options.nativeProtocols[protocol];\n if (!nativeProtocol) {\n throw new TypeError(\"Unsupported protocol \" + protocol);\n }\n\n // If specified, use the agent corresponding to the protocol\n // (HTTP and HTTPS use different types of agents)\n if (this._options.agents) {\n var scheme = protocol.slice(0, -1);\n this._options.agent = this._options.agents[scheme];\n }\n\n // Create the native request and set up its event handlers\n var request = this._currentRequest =\n nativeProtocol.request(this._options, this._onNativeResponse);\n request._redirectable = this;\n for (var event of events) {\n request.on(event, eventHandlers[event]);\n }\n\n // RFC7230§5.3.1: When making a request directly to an origin server, […]\n // a client MUST send only the absolute path […] as the request-target.\n this._currentUrl = /^\\//.test(this._options.path) ?\n url.format(this._options) :\n // When making a request to a proxy, […]\n // a client MUST send the target URI in absolute-form […].\n this._options.path;\n\n // End a redirected request\n // (The first request must be ended explicitly with RedirectableRequest#end)\n if (this._isRedirect) {\n // Write the request entity and end\n var i = 0;\n var self = this;\n var buffers = this._requestBodyBuffers;\n (function writeNext(error) {\n // Only write if this request has not been redirected yet\n // istanbul ignore else\n if (request === self._currentRequest) {\n // Report any write errors\n // istanbul ignore if\n if (error) {\n self.emit(\"error\", error);\n }\n // Write the next buffer if there are still left\n else if (i < buffers.length) {\n var buffer = buffers[i++];\n // istanbul ignore else\n if (!request.finished) {\n request.write(buffer.data, buffer.encoding, writeNext);\n }\n }\n // End the request if `end` has been called on us\n else if (self._ended) {\n request.end();\n }\n }\n }());\n }\n};\n\n// Processes a response from the current native request\nRedirectableRequest.prototype._processResponse = function (response) {\n // Store the redirected response\n var statusCode = response.statusCode;\n if (this._options.trackRedirects) {\n this._redirects.push({\n url: this._currentUrl,\n headers: response.headers,\n statusCode: statusCode,\n });\n }\n\n // RFC7231§6.4: The 3xx (Redirection) class of status code indicates\n // that further action needs to be taken by the user agent in order to\n // fulfill the request. If a Location header field is provided,\n // the user agent MAY automatically redirect its request to the URI\n // referenced by the Location field value,\n // even if the specific status code is not understood.\n\n // If the response is not a redirect; return it as-is\n var location = response.headers.location;\n if (!location || this._options.followRedirects === false ||\n statusCode < 300 || statusCode >= 400) {\n response.responseUrl = this._currentUrl;\n response.redirects = this._redirects;\n this.emit(\"response\", response);\n\n // Clean up\n this._requestBodyBuffers = [];\n return;\n }\n\n // The response is a redirect, so abort the current request\n destroyRequest(this._currentRequest);\n // Discard the remainder of the response to avoid waiting for data\n response.destroy();\n\n // RFC7231§6.4: A client SHOULD detect and intervene\n // in cyclical redirections (i.e., \"infinite\" redirection loops).\n if (++this._redirectCount > this._options.maxRedirects) {\n throw new TooManyRedirectsError();\n }\n\n // Store the request headers if applicable\n var requestHeaders;\n var beforeRedirect = this._options.beforeRedirect;\n if (beforeRedirect) {\n requestHeaders = Object.assign({\n // The Host header was set by nativeProtocol.request\n Host: response.req.getHeader(\"host\"),\n }, this._options.headers);\n }\n\n // RFC7231§6.4: Automatic redirection needs to done with\n // care for methods not known to be safe, […]\n // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change\n // the request method from POST to GET for the subsequent request.\n var method = this._options.method;\n if ((statusCode === 301 || statusCode === 302) && this._options.method === \"POST\" ||\n // RFC7231§6.4.4: The 303 (See Other) status code indicates that\n // the server is redirecting the user agent to a different resource […]\n // A user agent can perform a retrieval request targeting that URI\n // (a GET or HEAD request if using HTTP) […]\n (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {\n this._options.method = \"GET\";\n // Drop a possible entity and headers related to it\n this._requestBodyBuffers = [];\n removeMatchingHeaders(/^content-/i, this._options.headers);\n }\n\n // Drop the Host header, as the redirect might lead to a different host\n var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);\n\n // If the redirect is relative, carry over the host of the last request\n var currentUrlParts = parseUrl(this._currentUrl);\n var currentHost = currentHostHeader || currentUrlParts.host;\n var currentUrl = /^\\w+:/.test(location) ? this._currentUrl :\n url.format(Object.assign(currentUrlParts, { host: currentHost }));\n\n // Create the redirected request\n var redirectUrl = resolveUrl(location, currentUrl);\n debug(\"redirecting to\", redirectUrl.href);\n this._isRedirect = true;\n spreadUrlObject(redirectUrl, this._options);\n\n // Drop confidential headers when redirecting to a less secure protocol\n // or to a different domain that is not a superdomain\n if (redirectUrl.protocol !== currentUrlParts.protocol &&\n redirectUrl.protocol !== \"https:\" ||\n redirectUrl.host !== currentHost &&\n !isSubdomain(redirectUrl.host, currentHost)) {\n removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);\n }\n\n // Evaluate the beforeRedirect callback\n if (isFunction(beforeRedirect)) {\n var responseDetails = {\n headers: response.headers,\n statusCode: statusCode,\n };\n var requestDetails = {\n url: currentUrl,\n method: method,\n headers: requestHeaders,\n };\n beforeRedirect(this._options, responseDetails, requestDetails);\n this._sanitizeOptions(this._options);\n }\n\n // Perform the redirected request\n this._performRequest();\n};\n\n// Wraps the key/value object of protocols with redirect functionality\nfunction wrap(protocols) {\n // Default settings\n var exports = {\n maxRedirects: 21,\n maxBodyLength: 10 * 1024 * 1024,\n };\n\n // Wrap each protocol\n var nativeProtocols = {};\n Object.keys(protocols).forEach(function (scheme) {\n var protocol = scheme + \":\";\n var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];\n var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);\n\n // Executes a request, following redirects\n function request(input, options, callback) {\n // Parse parameters, ensuring that input is an object\n if (isURL(input)) {\n input = spreadUrlObject(input);\n }\n else if (isString(input)) {\n input = spreadUrlObject(parseUrl(input));\n }\n else {\n callback = options;\n options = validateUrl(input);\n input = { protocol: protocol };\n }\n if (isFunction(options)) {\n callback = options;\n options = null;\n }\n\n // Set defaults\n options = Object.assign({\n maxRedirects: exports.maxRedirects,\n maxBodyLength: exports.maxBodyLength,\n }, input, options);\n options.nativeProtocols = nativeProtocols;\n if (!isString(options.host) && !isString(options.hostname)) {\n options.hostname = \"::1\";\n }\n\n assert.equal(options.protocol, protocol, \"protocol mismatch\");\n debug(\"options\", options);\n return new RedirectableRequest(options, callback);\n }\n\n // Executes a GET request, following redirects\n function get(input, options, callback) {\n var wrappedRequest = wrappedProtocol.request(input, options, callback);\n wrappedRequest.end();\n return wrappedRequest;\n }\n\n // Expose the properties on the wrapped protocol\n Object.defineProperties(wrappedProtocol, {\n request: { value: request, configurable: true, enumerable: true, writable: true },\n get: { value: get, configurable: true, enumerable: true, writable: true },\n });\n });\n return exports;\n}\n\nfunction noop() { /* empty */ }\n\nfunction parseUrl(input) {\n var parsed;\n // istanbul ignore else\n if (useNativeURL) {\n parsed = new URL(input);\n }\n else {\n // Ensure the URL is valid and absolute\n parsed = validateUrl(url.parse(input));\n if (!isString(parsed.protocol)) {\n throw new InvalidUrlError({ input });\n }\n }\n return parsed;\n}\n\nfunction resolveUrl(relative, base) {\n // istanbul ignore next\n return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));\n}\n\nfunction validateUrl(input) {\n if (/^\\[/.test(input.hostname) && !/^\\[[:0-9a-f]+\\]$/i.test(input.hostname)) {\n throw new InvalidUrlError({ input: input.href || input });\n }\n if (/^\\[/.test(input.host) && !/^\\[[:0-9a-f]+\\](:\\d+)?$/i.test(input.host)) {\n throw new InvalidUrlError({ input: input.href || input });\n }\n return input;\n}\n\nfunction spreadUrlObject(urlObject, target) {\n var spread = target || {};\n for (var key of preservedUrlFields) {\n spread[key] = urlObject[key];\n }\n\n // Fix IPv6 hostname\n if (spread.hostname.startsWith(\"[\")) {\n spread.hostname = spread.hostname.slice(1, -1);\n }\n // Ensure port is a number\n if (spread.port !== \"\") {\n spread.port = Number(spread.port);\n }\n // Concatenate path\n spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;\n\n return spread;\n}\n\nfunction removeMatchingHeaders(regex, headers) {\n var lastValue;\n for (var header in headers) {\n if (regex.test(header)) {\n lastValue = headers[header];\n delete headers[header];\n }\n }\n return (lastValue === null || typeof lastValue === \"undefined\") ?\n undefined : String(lastValue).trim();\n}\n\nfunction createErrorType(code, message, baseClass) {\n // Create constructor\n function CustomError(properties) {\n // istanbul ignore else\n if (isFunction(Error.captureStackTrace)) {\n Error.captureStackTrace(this, this.constructor);\n }\n Object.assign(this, properties || {});\n this.code = code;\n this.message = this.cause ? message + \": \" + this.cause.message : message;\n }\n\n // Attach constructor and set default properties\n CustomError.prototype = new (baseClass || Error)();\n Object.defineProperties(CustomError.prototype, {\n constructor: {\n value: CustomError,\n enumerable: false,\n },\n name: {\n value: \"Error [\" + code + \"]\",\n enumerable: false,\n },\n });\n return CustomError;\n}\n\nfunction destroyRequest(request, error) {\n for (var event of events) {\n request.removeListener(event, eventHandlers[event]);\n }\n request.on(\"error\", noop);\n request.destroy(error);\n}\n\nfunction isSubdomain(subdomain, domain) {\n assert(isString(subdomain) && isString(domain));\n var dot = subdomain.length - domain.length - 1;\n return dot > 0 && subdomain[dot] === \".\" && subdomain.endsWith(domain);\n}\n\nfunction isString(value) {\n return typeof value === \"string\" || value instanceof String;\n}\n\nfunction isFunction(value) {\n return typeof value === \"function\";\n}\n\nfunction isBuffer(value) {\n return typeof value === \"object\" && (\"length\" in value);\n}\n\nfunction isURL(value) {\n return URL && value instanceof URL;\n}\n\n// Exports\nmodule.exports = wrap({ http: http, https: https });\nmodule.exports.wrap = wrap;\n","var debug;\n\nmodule.exports = function () {\n if (!debug) {\n try {\n /* eslint global-require: off */\n debug = require(\"debug\")(\"follow-redirects\");\n }\n catch (error) { /* */ }\n if (typeof debug !== \"function\") {\n debug = function () { /* */ };\n }\n }\n debug.apply(null, arguments);\n};\n","export function lowerCaseHeaders(headers: any) {\n return Object.keys(headers || {}).reduce((acc, header) => {\n acc[header.toLowerCase()] = headers[header]\n return acc\n }, {} as any)\n}\n","/**\n * Code borrowed from https://github.com/request/request\n * Apache License 2.0\n */\n\nimport url, {type UrlWithStringQuery} from 'url'\n\nimport type {ProxyOptions, RequestOptions} from '../../types'\n\nfunction formatHostname(hostname: string) {\n // canonicalize the hostname, so that 'oogle.com' won't match 'google.com'\n return hostname.replace(/^\\.*/, '.').toLowerCase()\n}\n\nfunction parseNoProxyZone(zoneStr: string) {\n const zone = zoneStr.trim().toLowerCase()\n\n const zoneParts = zone.split(':', 2)\n const zoneHost = formatHostname(zoneParts[0])\n const zonePort = zoneParts[1]\n const hasPort = zone.indexOf(':') > -1\n\n return {hostname: zoneHost, port: zonePort, hasPort: hasPort}\n}\n\nfunction uriInNoProxy(uri: UrlWithStringQuery, noProxy: string) {\n const port = uri.port || (uri.protocol === 'https:' ? '443' : '80')\n const hostname = formatHostname(uri.hostname || '')\n const noProxyList = noProxy.split(',')\n\n // iterate through the noProxyList until it finds a match.\n return noProxyList.map(parseNoProxyZone).some((noProxyZone) => {\n const isMatchedAt = hostname.indexOf(noProxyZone.hostname)\n const hostnameMatched =\n isMatchedAt > -1 && isMatchedAt === hostname.length - noProxyZone.hostname.length\n\n if (noProxyZone.hasPort) {\n return port === noProxyZone.port && hostnameMatched\n }\n\n return hostnameMatched\n })\n}\n\nfunction getProxyFromUri(uri: UrlWithStringQuery): string | null {\n // Decide the proper request proxy to use based on the request URI object and the\n // environmental variables (NO_PROXY, HTTP_PROXY, etc.)\n // respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)\n const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'] || ''\n\n // if the noProxy is a wildcard then return null\n if (noProxy === '*') {\n return null\n }\n\n // if the noProxy is not empty and the uri is found return null\n if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {\n return null\n }\n\n // Check for HTTP or HTTPS Proxy in environment, else default to null\n if (uri.protocol === 'http:') {\n return process.env['HTTP_PROXY'] || process.env['http_proxy'] || null\n }\n\n if (uri.protocol === 'https:') {\n return (\n process.env['HTTPS_PROXY'] ||\n process.env['https_proxy'] ||\n process.env['HTTP_PROXY'] ||\n process.env['http_proxy'] ||\n null\n )\n }\n\n // if none of that works, return null\n // (What uri protocol are you using then?)\n return null\n}\n\nfunction getHostFromUri(uri: UrlWithStringQuery) {\n let host = uri.host\n\n // Drop :port suffix from Host header if known protocol.\n if (uri.port) {\n if (\n (uri.port === '80' && uri.protocol === 'http:') ||\n (uri.port === '443' && uri.protocol === 'https:')\n ) {\n host = uri.hostname\n }\n }\n\n return host\n}\n\nfunction getHostHeaderWithPort(uri: UrlWithStringQuery) {\n const port = uri.port || (uri.protocol === 'https:' ? '443' : '80')\n return `${uri.hostname}:${port}`\n}\n\nexport function rewriteUriForProxy(\n reqOpts: RequestOptions & UrlWithStringQuery,\n uri: UrlWithStringQuery,\n proxy: UrlWithStringQuery | ProxyOptions,\n) {\n const headers = reqOpts.headers || {}\n const options = Object.assign({}, reqOpts, {headers})\n headers.host = headers.host || getHostHeaderWithPort(uri)\n options.protocol = proxy.protocol || options.protocol\n options.hostname = (\n proxy.host ||\n ('hostname' in proxy && proxy.hostname) ||\n options.hostname ||\n ''\n ).replace(/:\\d+/, '')\n options.port = proxy.port ? `${proxy.port}` : options.port\n options.host = getHostFromUri(Object.assign({}, uri, proxy))\n options.href = `${options.protocol}//${options.host}${options.path}`\n options.path = url.format(uri)\n return options\n}\n\nexport function getProxyOptions(options: RequestOptions): UrlWithStringQuery | ProxyOptions | null {\n const proxy =\n typeof options.proxy === 'undefined' ? getProxyFromUri(url.parse(options.url)) : options.proxy\n\n return typeof proxy === 'string' ? url.parse(proxy) : proxy || null\n}\n","/**\n * Code borrowed from https://github.com/request/request\n * Modified to be less request-specific, more functional\n * Apache License 2.0\n */\nimport * as tunnel from 'tunnel-agent'\nimport url from 'url'\n\nconst uriParts = [\n 'protocol',\n 'slashes',\n 'auth',\n 'host',\n 'port',\n 'hostname',\n 'hash',\n 'search',\n 'query',\n 'pathname',\n 'path',\n 'href',\n]\n\nconst defaultProxyHeaderWhiteList = [\n 'accept',\n 'accept-charset',\n 'accept-encoding',\n 'accept-language',\n 'accept-ranges',\n 'cache-control',\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-md5',\n 'content-range',\n 'content-type',\n 'connection',\n 'date',\n 'expect',\n 'max-forwards',\n 'pragma',\n 'referer',\n 'te',\n 'user-agent',\n 'via',\n]\n\nconst defaultProxyHeaderExclusiveList = ['proxy-authorization']\n\nexport function shouldEnable(options: any) {\n // Tunnel HTTPS by default. Allow the user to override this setting.\n\n // If user has specified a specific tunnel override...\n if (typeof options.tunnel !== 'undefined') {\n return Boolean(options.tunnel)\n }\n\n // If the destination is HTTPS, tunnel.\n const uri = url.parse(options.url)\n if (uri.protocol === 'https:') {\n return true\n }\n\n // Otherwise, do not use tunnel.\n return false\n}\n\nexport function applyAgent(opts: any = {}, proxy: any) {\n const options = Object.assign({}, opts)\n\n // Setup proxy header exclusive list and whitelist\n const proxyHeaderWhiteList = defaultProxyHeaderWhiteList\n .concat(options.proxyHeaderWhiteList || [])\n .map((header) => header.toLowerCase())\n\n const proxyHeaderExclusiveList = defaultProxyHeaderExclusiveList\n .concat(options.proxyHeaderExclusiveList || [])\n .map((header) => header.toLowerCase())\n\n // Get the headers we should send to the proxy\n const proxyHeaders = getAllowedProxyHeaders(options.headers, proxyHeaderWhiteList)\n proxyHeaders.host = constructProxyHost(options)\n\n // Reduce headers to the ones not exclusive for the proxy\n options.headers = Object.keys(options.headers || {}).reduce((headers, header) => {\n const isAllowed = proxyHeaderExclusiveList.indexOf(header.toLowerCase()) === -1\n if (isAllowed) {\n headers[header] = options.headers[header]\n }\n\n return headers\n }, {} as any)\n\n const tunnelFn = getTunnelFn(options, proxy)\n const tunnelOptions = constructTunnelOptions(options, proxy, proxyHeaders)\n options.agent = tunnelFn(tunnelOptions)\n\n return options\n}\n\nfunction getTunnelFn(options: any, proxy: any) {\n const uri = getUriParts(options)\n const tunnelFnName = constructTunnelFnName(uri, proxy)\n return tunnel[tunnelFnName]\n}\n\nfunction getUriParts(options: any) {\n return uriParts.reduce((uri, part) => {\n uri[part] = options[part]\n return uri\n }, {} as any)\n}\n\ntype UriProtocol = `http` | `https`\ntype ProxyProtocol = `Http` | `Https`\nfunction constructTunnelFnName(uri: any, proxy: any): `${UriProtocol}Over${ProxyProtocol}` {\n const uriProtocol = uri.protocol === 'https:' ? 'https' : 'http'\n const proxyProtocol = proxy.protocol === 'https:' ? 'Https' : 'Http'\n return `${uriProtocol}Over${proxyProtocol}`\n}\n\nfunction constructProxyHost(uri: any) {\n const port = uri.port\n const protocol = uri.protocol\n let proxyHost = `${uri.hostname}:`\n\n if (port) {\n proxyHost += port\n } else if (protocol === 'https:') {\n proxyHost += '443'\n } else {\n proxyHost += '80'\n }\n\n return proxyHost\n}\n\nfunction getAllowedProxyHeaders(headers: any, whiteList: any): any {\n return Object.keys(headers)\n .filter((header) => whiteList.indexOf(header.toLowerCase()) !== -1)\n .reduce((set: any, header: any) => {\n set[header] = headers[header]\n return set\n }, {})\n}\n\nfunction constructTunnelOptions(options: any, proxy: any, proxyHeaders: any) {\n return {\n proxy: {\n host: proxy.hostname,\n port: +proxy.port,\n proxyAuth: proxy.auth,\n headers: proxyHeaders,\n },\n headers: options.headers,\n ca: options.ca,\n cert: options.cert,\n key: options.key,\n passphrase: options.passphrase,\n pfx: options.pfx,\n ciphers: options.ciphers,\n rejectUnauthorized: options.rejectUnauthorized,\n secureOptions: options.secureOptions,\n secureProtocol: options.secureProtocol,\n }\n}\n","import decompressResponse from 'decompress-response'\nimport follow, {type FollowResponse, type RedirectableRequest} from 'follow-redirects'\nimport type {FinalizeNodeOptionsPayload, HttpRequest, MiddlewareResponse} from 'get-it'\nimport http from 'http'\nimport https from 'https'\nimport qs from 'querystring'\nimport {Readable, type Stream} from 'stream'\nimport url from 'url'\n\nimport type {RequestAdapter} from '../types'\nimport {lowerCaseHeaders} from '../util/lowerCaseHeaders'\nimport {progressStream} from '../util/progress-stream'\nimport {getProxyOptions, rewriteUriForProxy} from './node/proxy'\nimport {concat} from './node/simpleConcat'\nimport {timedOut} from './node/timedOut'\nimport * as tunneling from './node/tunnel'\nimport {NodeRequestError} from './node-request-error'\n\n/**\n * Taken from:\n * https://github.com/sindresorhus/is-stream/blob/fb8caed475b4107cee3c22be3252a904020eb2d4/index.js#L3-L6\n */\nconst isStream = (stream: any): stream is Stream =>\n stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'\n\n/** @public */\nexport const adapter: RequestAdapter = 'node'\n\n// Reduce a fully fledged node-style response object to\n// something that works in both browser and node environment\nconst reduceResponse = (\n res: http.IncomingMessage,\n remoteAddress: string | undefined,\n reqUrl: string,\n method: string,\n body: any,\n): MiddlewareResponse => ({\n body,\n url: reqUrl,\n method: method,\n headers: res.headers,\n statusCode: res.statusCode || 0,\n statusMessage: res.statusMessage || '',\n remoteAddress,\n})\n\nexport const httpRequester: HttpRequest = (context, cb) => {\n const {options} = context\n const uri = Object.assign({}, url.parse(options.url))\n\n if (typeof fetch === 'function' && options.fetch) {\n const controller = new AbortController()\n const reqOpts = context.applyMiddleware('finalizeOptions', {\n ...uri,\n method: options.method,\n headers: {\n ...(typeof options.fetch === 'object' && options.fetch.headers\n ? lowerCaseHeaders(options.fetch.headers)\n : {}),\n ...lowerCaseHeaders(options.headers),\n },\n maxRedirects: options.maxRedirects,\n }) as FinalizeNodeOptionsPayload\n const fetchOpts = {\n credentials: options.withCredentials ? 'include' : 'omit',\n ...(typeof options.fetch === 'object' ? options.fetch : {}),\n method: reqOpts.method,\n headers: reqOpts.headers,\n body: options.body,\n signal: controller.signal,\n } satisfies RequestInit\n\n // Allow middleware to inject a response, for instance in the case of caching or mocking\n const injectedResponse = context.applyMiddleware('interceptRequest', undefined, {\n adapter,\n context,\n })\n\n // If middleware injected a response, treat it as we normally would and return it\n // Do note that the injected response has to be reduced to a cross-environment friendly response\n if (injectedResponse) {\n const cbTimer = setTimeout(cb, 0, null, injectedResponse)\n const cancel = () => clearTimeout(cbTimer)\n return {abort: cancel}\n }\n\n const request = fetch(options.url, fetchOpts)\n\n // Let middleware know we're about to do a request\n context.applyMiddleware('onRequest', {options, adapter, request, context})\n\n request\n .then(async (res) => {\n const body = options.rawBody ? res.body : await res.text()\n\n const headers = {} as Record<string, string>\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n cb(null, {\n body,\n url: res.url,\n method: options.method!,\n headers,\n statusCode: res.status,\n statusMessage: res.statusText,\n })\n })\n .catch((err) => {\n if (err.name == 'AbortError') return\n cb(err)\n })\n\n return {abort: () => controller.abort()}\n }\n\n const bodyType = isStream(options.body) ? 'stream' : typeof options.body\n if (\n bodyType !== 'undefined' &&\n bodyType !== 'stream' &&\n bodyType !== 'string' &&\n !Buffer.isBuffer(options.body)\n ) {\n throw new Error(`Request body must be a string, buffer or stream, got ${bodyType}`)\n }\n\n const lengthHeader: any = {}\n if (options.bodySize) {\n lengthHeader['content-length'] = options.bodySize\n } else if (options.body && bodyType !== 'stream') {\n lengthHeader['content-length'] = Buffer.byteLength(options.body)\n }\n\n // Make sure callback is not called in the event of a cancellation\n let aborted = false\n const callback = (err: Error | null, res?: MiddlewareResponse) => !aborted && cb(err, res)\n context.channels.abort.subscribe(() => {\n aborted = true\n })\n\n // Create a reduced subset of options meant for the http.request() method\n let reqOpts: any = Object.assign({}, uri, {\n method: options.method,\n headers: Object.assign({}, lowerCaseHeaders(options.headers), lengthHeader),\n maxRedirects: options.maxRedirects,\n })\n\n // Figure out proxying/tunnel options\n const proxy = getProxyOptions(options)\n const tunnel = proxy && tunneling.shouldEnable(options)\n\n // Allow middleware to inject a response, for instance in the case of caching or mocking\n const injectedResponse = context.applyMiddleware('interceptRequest', undefined, {\n adapter,\n context,\n })\n\n // If middleware injected a response, treat it as we normally would and return it\n // Do note that the injected response has to be reduced to a cross-environment friendly response\n if (injectedResponse) {\n const cbTimer = setImmediate(callback, null, injectedResponse)\n const abort = () => clearImmediate(cbTimer)\n return {abort}\n }\n\n // We're using the follow-redirects module to transparently follow redirects\n if (options.maxRedirects !== 0) {\n reqOpts.maxRedirects = options.maxRedirects || 5\n }\n\n // Apply currect options for proxy tunneling, if enabled\n if (proxy && tunnel) {\n reqOpts = tunneling.applyAgent(reqOpts, proxy)\n } else if (proxy && !tunnel) {\n reqOpts = rewriteUriForProxy(reqOpts, uri, proxy)\n }\n\n // Handle proxy authorization if present\n if (!tunnel && proxy && proxy.auth && !reqOpts.headers['proxy-authorization']) {\n const [username, password] =\n typeof proxy.auth === 'string'\n ? proxy.auth.split(':').map((item) => qs.unescape(item))\n : [proxy.auth.username, proxy.auth.password]\n\n const auth = Buffer.from(`${username}:${password}`, 'utf8')\n const authBase64 = auth.toString('base64')\n reqOpts.headers['proxy-authorization'] = `Basic ${authBase64}`\n }\n\n // Figure out transport (http/https, forwarding/non-forwarding agent)\n const transport = getRequestTransport(reqOpts, proxy, tunnel)\n if (typeof options.debug === 'function' && proxy) {\n options.debug(\n 'Proxying using %s',\n reqOpts.agent ? 'tunnel agent' : `${reqOpts.host}:${reqOpts.port}`,\n )\n }\n\n // See if we should try to request a compressed response (and decompress on return)\n const tryCompressed = reqOpts.method !== 'HEAD'\n if (tryCompressed && !reqOpts.headers['accept-encoding'] && options.compress !== false) {\n reqOpts.headers['accept-encoding'] =\n // Workaround Bun not supporting brotli: https://github.com/oven-sh/bun/issues/267\n typeof Bun !== 'undefined' ? 'gzip, deflate' : 'br, gzip, deflate'\n }\n\n let _res: http.IncomingMessage | undefined\n const finalOptions = context.applyMiddleware(\n 'finalizeOptions',\n reqOpts,\n ) as FinalizeNodeOptionsPayload\n const request = transport.request(finalOptions, (response) => {\n // Snapshot emptiness before decompressResponse/middleware pipe the\n // IncomingMessage. At this point readableLength reflects exactly what the\n // HTTP parser has buffered, with no async-transform ambiguity.\n const bodyIsKnownEmpty = response.complete && response.readableLength === 0\n\n const res = tryCompressed ? decompressResponse(response) : response\n _res = res\n const resStream = context.applyMiddleware('onHeaders', res, {\n headers: response.headers,\n adapter,\n context,\n })\n\n // On redirects, `responseUrl` is set\n const reqUrl = 'responseUrl' in response ? response.responseUrl : options.url\n // Get the remote address from the socket, if available. After the stream is consumed, the socket might be closed, so we grab it here.\n const remoteAddress = res.socket?.remoteAddress\n\n if (options.stream) {\n callback(null, reduceResponse(res, remoteAddress, reqUrl, reqOpts.method, resStream))\n\n // When the response body is empty, the stream must still be drained so\n // the 'end' event fires. For unpiped responses this also releases the\n // socket; for piped responses (decompress-response / onHeaders) the pipe\n // chain already consumes the IncomingMessage, but resStream still needs\n // to be drained for 'end' to emit.\n //\n // We prefer the `bodyIsKnownEmpty` snapshot (captured before any piping)\n // because it checks the raw IncomingMessage and is immune to async\n // Transform ambiguity (e.g. zlib where readableLength on the output can\n // be 0 while decompression is still pending).\n //\n // For chunked responses the HTTP parser may not have set `complete` by\n // the time the response callback fires. In that case we fall back to\n // checking the original IncomingMessage in nextTick, but only when it\n // has NOT been piped (readableFlowing !== true) — piping drains\n // readableLength to 0 even for non-empty bodies.\n //\n // When the response was piped through a transform (decompress-response\n // or onHeaders middleware), neither check above works. We instead wait\n // for 'readable' on resStream and peek one byte: null means the stream\n // ended empty, so we resume; non-null means real data, so we unshift it\n // back for the caller to consume.\n process.nextTick(() => {\n if (resStream.readableFlowing) {\n return\n }\n\n const isEmpty =\n bodyIsKnownEmpty ||\n (response.complete && response.readableLength === 0 && !response.readableFlowing)\n\n if (isEmpty) {\n resStream.resume()\n return\n }\n\n // Piped case: response was consumed by decompress-response or\n // onHeaders middleware, so we cannot inspect readableLength on the\n // original IncomingMessage. Peek via 'readable' instead.\n if (response.complete && response.readableFlowing) {\n resStream.once('readable', () => {\n if (resStream.readableFlowing) {\n return\n }\n const chunk = resStream.read(1)\n if (chunk === null) {\n resStream.resume()\n } else {\n resStream.unshift(chunk)\n }\n })\n }\n })\n\n return\n }\n\n // Concatenate the response body, then parse the response with middlewares\n concat(resStream, (err: any, data: any) => {\n if (err) {\n return callback(err)\n }\n\n const body = options.rawBody ? data : data.toString()\n const reduced = reduceResponse(res, remoteAddress, reqUrl, reqOpts.method, body)\n return callback(null, reduced)\n })\n })\n\n function onError(err: NodeJS.ErrnoException) {\n // HACK: If we have a socket error, and response has already been assigned this means\n // that a response has already been sent. According to node.js docs, this is\n // will result in the response erroring with an error code of 'ECONNRESET'.\n // We first destroy the response, then the request, with the same error. This way the\n // error is forwarded to both the response and the request.\n // See the event order outlined here https://nodejs.org/api/http.html#httprequesturl-options-callback for how node.js handles the different scenarios.\n if (_res) _res.destroy(err)\n request.destroy(err)\n }\n\n request.once('socket', (socket: NodeJS.Socket) => {\n socket.once('error', onError)\n request.once('response', (response) => {\n response.once('end', () => {\n s