import process from 'node:process'; import { promisify, inspect } from 'node:util'; import { checkServerIdentity } from 'node:tls'; // DO NOT use destructuring for `https.request` and `http.request` as it's not compatible with `nock`. import http from 'node:http'; import https from 'node:https'; import is, { assert } from '@sindresorhus/is'; import lowercaseKeys from 'lowercase-keys'; import CacheableLookup from 'cacheable-lookup'; import http2wrapper from 'http2-wrapper'; import { isFormData } from 'form-data-encoder'; import parseLinkHeader from './parse-link-header.js'; const [major, minor] = process.versions.node.split('.').map(Number); function validateSearchParameters(searchParameters) { // eslint-disable-next-line guard-for-in for (const key in searchParameters) { const value = searchParameters[key]; assert.any([is.string, is.number, is.boolean, is.null_, is.undefined], value); } } const globalCache = new Map(); let globalDnsCache; const getGlobalDnsCache = () => { if (globalDnsCache) { return globalDnsCache; } globalDnsCache = new CacheableLookup(); return globalDnsCache; }; const defaultInternals = { request: undefined, agent: { http: undefined, https: undefined, http2: undefined, }, h2session: undefined, decompress: true, timeout: { connect: undefined, lookup: undefined, read: undefined, request: undefined, response: undefined, secureConnect: undefined, send: undefined, socket: undefined, }, prefixUrl: '', body: undefined, form: undefined, json: undefined, cookieJar: undefined, ignoreInvalidCookies: false, searchParams: undefined, dnsLookup: undefined, dnsCache: undefined, context: {}, hooks: { init: [], beforeRequest: [], beforeError: [], beforeRedirect: [], beforeRetry: [], afterResponse: [], }, followRedirect: true, maxRedirects: 10, cache: undefined, throwHttpErrors: true, username: '', password: '', http2: false, allowGetBody: false, headers: { 'user-agent': 'got (https://github.com/sindresorhus/got)', }, methodRewriting: false, dnsLookupIpVersion: undefined, parseJson: JSON.parse, stringifyJson: JSON.stringify, retry: { limit: 2, methods: [ 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE', ], statusCodes: [ 408, 413, 429, 500, 502, 503, 504, 521, 522, 524, ], errorCodes: [ 'ETIMEDOUT', 'ECONNRESET', 'EADDRINUSE', 'ECONNREFUSED', 'EPIPE', 'ENOTFOUND', 'ENETUNREACH', 'EAI_AGAIN', ], maxRetryAfter: undefined, calculateDelay: ({ computedValue }) => computedValue, backoffLimit: Number.POSITIVE_INFINITY, noise: 100, }, localAddress: undefined, method: 'GET', createConnection: undefined, cacheOptions: { shared: undefined, cacheHeuristic: undefined, immutableMinTimeToLive: undefined, ignoreCargoCult: undefined, }, https: { alpnProtocols: undefined, rejectUnauthorized: undefined, checkServerIdentity: undefined, certificateAuthority: undefined, key: undefined, certificate: undefined, passphrase: undefined, pfx: undefined, ciphers: undefined, honorCipherOrder: undefined, minVersion: undefined, maxVersion: undefined, signatureAlgorithms: undefined, tlsSessionLifetime: undefined, dhparam: undefined, ecdhCurve: undefined, certificateRevocationLists: undefined, }, encoding: undefined, resolveBodyOnly: false, isStream: false, responseType: 'text', url: undefined, pagination: { transform(response) { if (response.request.options.responseType === 'json') { return response.body; } return JSON.parse(response.body); }, paginate({ response }) { const rawLinkHeader = response.headers.link; if (typeof rawLinkHeader !== 'string' || rawLinkHeader.trim() === '') { return false; } const parsed = parseLinkHeader(rawLinkHeader); const next = parsed.find(entry => entry.parameters.rel === 'next' || entry.parameters.rel === '"next"'); if (next) { return { url: new URL(next.reference, response.url), }; } return false; }, filter: () => true, shouldContinue: () => true, countLimit: Number.POSITIVE_INFINITY, backoff: 0, requestLimit: 10000, stackAllItems: false, }, setHost: true, maxHeaderSize: undefined, signal: undefined, enableUnixSockets: false, }; const cloneInternals = (internals) => { const { hooks, retry } = internals; const result = { ...internals, context: { ...internals.context }, cacheOptions: { ...internals.cacheOptions }, https: { ...internals.https }, agent: { ...internals.agent }, headers: { ...internals.headers }, retry: { ...retry, errorCodes: [...retry.errorCodes], methods: [...retry.methods], statusCodes: [...retry.statusCodes], }, timeout: { ...internals.timeout }, hooks: { init: [...hooks.init], beforeRequest: [...hooks.beforeRequest], beforeError: [...hooks.beforeError], beforeRedirect: [...hooks.beforeRedirect], beforeRetry: [...hooks.beforeRetry], afterResponse: [...hooks.afterResponse], }, searchParams: internals.searchParams ? new URLSearchParams(internals.searchParams) : undefined, pagination: { ...internals.pagination }, }; if (result.url !== undefined) { result.prefixUrl = ''; } return result; }; const cloneRaw = (raw) => { const { hooks, retry } = raw; const result = { ...raw }; if (is.object(raw.context)) { result.context = { ...raw.context }; } if (is.object(raw.cacheOptions)) { result.cacheOptions = { ...raw.cacheOptions }; } if (is.object(raw.https)) { result.https = { ...raw.https }; } if (is.object(raw.cacheOptions)) { result.cacheOptions = { ...result.cacheOptions }; } if (is.object(raw.agent)) { result.agent = { ...raw.agent }; } if (is.object(raw.headers)) { result.headers = { ...raw.headers }; } if (is.object(retry)) { result.retry = { ...retry }; if (is.array(retry.errorCodes)) { result.retry.errorCodes = [...retry.errorCodes]; } if (is.array(retry.methods)) { result.retry.methods = [...retry.methods]; } if (is.array(retry.statusCodes)) { result.retry.statusCodes = [...retry.statusCodes]; } } if (is.object(raw.timeout)) { result.timeout = { ...raw.timeout }; } if (is.object(hooks)) { result.hooks = { ...hooks, }; if (is.array(hooks.init)) { result.hooks.init = [...hooks.init]; } if (is.array(hooks.beforeRequest)) { result.hooks.beforeRequest = [...hooks.beforeRequest]; } if (is.array(hooks.beforeError)) { result.hooks.beforeError = [...hooks.beforeError]; } if (is.array(hooks.beforeRedirect)) { result.hooks.beforeRedirect = [...hooks.beforeRedirect]; } if (is.array(hooks.beforeRetry)) { result.hooks.beforeRetry = [...hooks.beforeRetry]; } if (is.array(hooks.afterResponse)) { result.hooks.afterResponse = [...hooks.afterResponse]; } } // TODO: raw.searchParams if (is.object(raw.pagination)) { result.pagination = { ...raw.pagination }; } return result; }; const getHttp2TimeoutOption = (internals) => { const delays = [internals.timeout.socket, internals.timeout.connect, internals.timeout.lookup, internals.timeout.request, internals.timeout.secureConnect].filter(delay => typeof delay === 'number'); if (delays.length > 0) { return Math.min(...delays); } return undefined; }; const init = (options, withOptions, self) => { const initHooks = options.hooks?.init; if (initHooks) { for (const hook of initHooks) { hook(withOptions, self); } } }; export default class Options { constructor(input, options, defaults) { Object.defineProperty(this, "_unixOptions", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_internals", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_merging", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_init", { enumerable: true, configurable: true, writable: true, value: void 0 }); assert.any([is.string, is.urlInstance, is.object, is.undefined], input); assert.any([is.object, is.undefined], options); assert.any([is.object, is.undefined], defaults); if (input instanceof Options || options instanceof Options) { throw new TypeError('The defaults must be passed as the third argument'); } this._internals = cloneInternals(defaults?._internals ?? defaults ?? defaultInternals); this._init = [...(defaults?._init ?? [])]; this._merging = false; this._unixOptions = undefined; // This rule allows `finally` to be considered more important. // Meaning no matter the error thrown in the `try` block, // if `finally` throws then the `finally` error will be thrown. // // Yes, we want this. If we set `url` first, then the `url.searchParams` // would get merged. Instead we set the `searchParams` first, then // `url.searchParams` is overwritten as expected. // /* eslint-disable no-unsafe-finally */ try { if (is.plainObject(input)) { try { this.merge(input); this.merge(options); } finally { this.url = input.url; } } else { try { this.merge(options); } finally { if (options?.url !== undefined) { if (input === undefined) { this.url = options.url; } else { throw new TypeError('The `url` option is mutually exclusive with the `input` argument'); } } else if (input !== undefined) { this.url = input; } } } } catch (error) { error.options = this; throw error; } /* eslint-enable no-unsafe-finally */ } merge(options) { if (!options) { return; } if (options instanceof Options) { for (const init of options._init) { this.merge(init); } return; } options = cloneRaw(options); init(this, options, this); init(options, options, this); this._merging = true; // Always merge `isStream` first if ('isStream' in options) { this.isStream = options.isStream; } try { let push = false; for (const key in options) { // `got.extend()` options if (key === 'mutableDefaults' || key === 'handlers') { continue; } // Never merge `url` if (key === 'url') { continue; } if (!(key in this)) { throw new Error(`Unexpected option: ${key}`); } // @ts-expect-error Type 'unknown' is not assignable to type 'never'. const value = options[key]; if (value === undefined) { continue; } // @ts-expect-error Type 'unknown' is not assignable to type 'never'. this[key] = value; push = true; } if (push) { this._init.push(options); } } finally { this._merging = false; } } /** Custom request function. The main purpose of this is to [support HTTP2 using a wrapper](https://github.com/szmarczak/http2-wrapper). @default http.request | https.request */ get request() { return this._internals.request; } set request(value) { assert.any([is.function_, is.undefined], value); this._internals.request = value; } /** An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance. This is necessary because a request to one protocol might redirect to another. In such a scenario, Got will switch over to the right protocol agent for you. If a key is not present, it will default to a global agent. @example ``` import got from 'got'; import HttpAgent from 'agentkeepalive'; const {HttpsAgent} = HttpAgent; await got('https://sindresorhus.com', { agent: { http: new HttpAgent(), https: new HttpsAgent() } }); ``` */ get agent() { return this._internals.agent; } set agent(value) { assert.plainObject(value); // eslint-disable-next-line guard-for-in for (const key in value) { if (!(key in this._internals.agent)) { throw new TypeError(`Unexpected agent option: ${key}`); } // @ts-expect-error - No idea why `value[key]` doesn't work here. assert.any([is.object, is.undefined], value[key]); } if (this._merging) { Object.assign(this._internals.agent, value); } else { this._internals.agent = { ...value }; } } get h2session() { return this._internals.h2session; } set h2session(value) { this._internals.h2session = value; } /** Decompress the response automatically. This will set the `accept-encoding` header to `gzip, deflate, br` unless you set it yourself. If this is disabled, a compressed response is returned as a `Buffer`. This may be useful if you want to handle decompression yourself or stream the raw compressed data. @default true */ get decompress() { return this._internals.decompress; } set decompress(value) { assert.boolean(value); this._internals.decompress = value; } /** Milliseconds to wait for the server to end the response before aborting the request with `got.TimeoutError` error (a.k.a. `request` property). By default, there's no timeout. This also accepts an `object` with the following fields to constrain the duration of each phase of the request lifecycle: - `lookup` starts when a socket is assigned and ends when the hostname has been resolved. Does not apply when using a Unix domain socket. - `connect` starts when `lookup` completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected. - `secureConnect` starts when `connect` completes and ends when the handshaking process completes (HTTPS only). - `socket` starts when the socket is connected. See [request.setTimeout](https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback). - `response` starts when the request has been written to the socket and ends when the response headers are received. - `send` starts when the socket is connected and ends with the request has been written to the socket. - `request` starts when the request is initiated and ends when the response's end event fires. */ get timeout() { // We always return `Delays` here. // It has to be `Delays | number`, otherwise TypeScript will error because the getter and the setter have incompatible types. return this._internals.timeout; } set timeout(value) { assert.plainObject(value); // eslint-disable-next-line guard-for-in for (const key in value) { if (!(key in this._internals.timeout)) { throw new Error(`Unexpected timeout option: ${key}`); } // @ts-expect-error - No idea why `value[key]` doesn't work here. assert.any([is.number, is.undefined], value[key]); } if (this._merging) { Object.assign(this._internals.timeout, value); } else { this._internals.timeout = { ...value }; } } /** When specified, `prefixUrl` will be prepended to `url`. The prefix can be any valid URL, either relative or absolute. A trailing slash `/` is optional - one will be added automatically. __Note__: `prefixUrl` will be ignored if the `url` argument is a URL instance. __Note__: Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion. For example, when the prefix URL is `https://example.com/foo` and the input is `/bar`, there's ambiguity whether the resulting URL would become `https://example.com/foo/bar` or `https://example.com/bar`. The latter is used by browsers. __Tip__: Useful when used with `got.extend()` to create niche-specific Got instances. __Tip__: You can change `prefixUrl` using hooks as long as the URL still includes the `prefixUrl`. If the URL doesn't include it anymore, it will throw. @example ``` import got from 'got'; await got('unicorn', {prefixUrl: 'https://cats.com'}); //=> 'https://cats.com/unicorn' const instance = got.extend({ prefixUrl: 'https://google.com' }); await instance('unicorn', { hooks: { beforeRequest: [ options => { options.prefixUrl = 'https://cats.com'; } ] } }); //=> 'https://cats.com/unicorn' ``` */ get prefixUrl() { // We always return `string` here. // It has to be `string | URL`, otherwise TypeScript will error because the getter and the setter have incompatible types. return this._internals.prefixUrl; } set prefixUrl(value) { assert.any([is.string, is.urlInstance], value); if (value === '') { this._internals.prefixUrl = ''; return; } value = value.toString(); if (!value.endsWith('/')) { value += '/'; } if (this._internals.prefixUrl && this._internals.url) { const { href } = this._internals.url; this._internals.url.href = value + href.slice(this._internals.prefixUrl.length); } this._internals.prefixUrl = value; } /** __Note #1__: The `body` option cannot be used with the `json` or `form` option. __Note #2__: If you provide this option, `got.stream()` will be read-only. __Note #3__: If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError` unless the method is `GET` and the `allowGetBody` option is set to `true`. __Note #4__: This option is not enumerable and will not be merged with the instance defaults. The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`. Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`. */ get body() { return this._internals.body; } set body(value) { assert.any([is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, isFormData, is.undefined], value); if (is.nodeStream(value)) { assert.truthy(value.readable); } if (value !== undefined) { assert.undefined(this._internals.form); assert.undefined(this._internals.json); } this._internals.body = value; } /** The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj). If the `Content-Type` header is not present, it will be set to `application/x-www-form-urlencoded`. __Note #1__: If you provide this option, `got.stream()` will be read-only. __Note #2__: This option is not enumerable and will not be merged with the instance defaults. */ get form() { return this._internals.form; } set form(value) { assert.any([is.plainObject, is.undefined], value); if (value !== undefined) { assert.undefined(this._internals.body); assert.undefined(this._internals.json); } this._internals.form = value; } /** JSON body. If the `Content-Type` header is not set, it will be set to `application/json`. __Note #1__: If you provide this option, `got.stream()` will be read-only. __Note #2__: This option is not enumerable and will not be merged with the instance defaults. */ get json() { return this._internals.json; } set json(value) { if (value !== undefined) { assert.undefined(this._internals.body); assert.undefined(this._internals.form); } this._internals.json = value; } /** The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url). Properties from `options` will override properties in the parsed `url`. If no protocol is specified, it will throw a `TypeError`. __Note__: The query string is **not** parsed as search params. @example ``` await got('https://example.com/?query=a b'); //=> https://example.com/?query=a%20b await got('https://example.com/', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b // The query string is overridden by `searchParams` await got('https://example.com/?query=a b', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b ``` */ get url() { return this._internals.url; } set url(value) { assert.any([is.string, is.urlInstance, is.undefined], value); if (value === undefined) { this._internals.url = undefined; return; } if (is.string(value) && value.startsWith('/')) { throw new Error('`url` must not start with a slash'); } const urlString = `${this.prefixUrl}${value.toString()}`; const url = new URL(urlString); this._internals.url = url; if (url.protocol === 'unix:') { url.href = `http://unix${url.pathname}${url.search}`; } if (url.protocol !== 'http:' && url.protocol !== 'https:') { const error = new Error(`Unsupported protocol: ${url.protocol}`); error.code = 'ERR_UNSUPPORTED_PROTOCOL'; throw error; } if (this._internals.username) { url.username = this._internals.username; this._internals.username = ''; } if (this._internals.password) { url.password = this._internals.password; this._internals.password = ''; } if (this._internals.searchParams) { url.search = this._internals.searchParams.toString(); this._internals.searchParams = undefined; } if (url.hostname === 'unix') { if (!this._internals.enableUnixSockets) { throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled'); } const matches = /(?.+?):(?.+)/.exec(`${url.pathname}${url.search}`); if (matches?.groups) { const { socketPath, path } = matches.groups; this._unixOptions = { socketPath, path, host: '', }; } else { this._unixOptions = undefined; } return; } this._unixOptions = undefined; } /** Cookie support. You don't have to care about parsing or how to store them. __Note__: If you provide this option, `options.headers.cookie` will be overridden. */ get cookieJar() { return this._internals.cookieJar; } set cookieJar(value) { assert.any([is.object, is.undefined], value); if (value === undefined) { this._internals.cookieJar = undefined; return; } let { setCookie, getCookieString } = value; assert.function_(setCookie); assert.function_(getCookieString); /* istanbul ignore next: Horrible `tough-cookie` v3 check */ if (setCookie.length === 4 && getCookieString.length === 0) { setCookie = promisify(setCookie.bind(value)); getCookieString = promisify(getCookieString.bind(value)); this._internals.cookieJar = { setCookie, getCookieString: getCookieString, }; } else { this._internals.cookieJar = value; } } /** You can abort the `request` using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). @example ``` import got from 'got'; const abortController = new AbortController(); const request = got('https://httpbin.org/anything', { signal: abortController.signal }); setTimeout(() => { abortController.abort(); }, 100); ``` */ get signal() { return this._internals.signal; } set signal(value) { assert.object(value); this._internals.signal = value; } /** Ignore invalid cookies instead of throwing an error. Only useful when the `cookieJar` option has been set. Not recommended. @default false */ get ignoreInvalidCookies() { return this._internals.ignoreInvalidCookies; } set ignoreInvalidCookies(value) { assert.boolean(value); this._internals.ignoreInvalidCookies = value; } /** Query string that will be added to the request URL. This will override the query string in `url`. If you need to pass in an array, you can do it using a `URLSearchParams` instance. @example ``` import got from 'got'; const searchParams = new URLSearchParams([['key', 'a'], ['key', 'b']]); await got('https://example.com', {searchParams}); console.log(searchParams.toString()); //=> 'key=a&key=b' ``` */ get searchParams() { if (this._internals.url) { return this._internals.url.searchParams; } if (this._internals.searchParams === undefined) { this._internals.searchParams = new URLSearchParams(); } return this._internals.searchParams; } set searchParams(value) { assert.any([is.string, is.object, is.undefined], value); const url = this._internals.url; if (value === undefined) { this._internals.searchParams = undefined; if (url) { url.search = ''; } return; } const searchParameters = this.searchParams; let updated; if (is.string(value)) { updated = new URLSearchParams(value); } else if (value instanceof URLSearchParams) { updated = value; } else { validateSearchParameters(value); updated = new URLSearchParams(); // eslint-disable-next-line guard-for-in for (const key in value) { const entry = value[key]; if (entry === null) { updated.append(key, ''); } else if (entry === undefined) { searchParameters.delete(key); } else { updated.append(key, entry); } } } if (this._merging) { // These keys will be replaced for (const key of updated.keys()) { searchParameters.delete(key); } for (const [key, value] of updated) { searchParameters.append(key, value); } } else if (url) { url.search = searchParameters.toString(); } else { this._internals.searchParams = searchParameters; } } get searchParameters() { throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.'); } set searchParameters(_value) { throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.'); } get dnsLookup() { return this._internals.dnsLookup; } set dnsLookup(value) { assert.any([is.function_, is.undefined], value); this._internals.dnsLookup = value; } /** An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups. Useful when making lots of requests to different *public* hostnames. `CacheableLookup` uses `dns.resolver4(..)` and `dns.resolver6(...)` under the hood and fall backs to `dns.lookup(...)` when the first two fail, which may lead to additional delay. __Note__: This should stay disabled when making requests to internal hostnames such as `localhost`, `database.local` etc. @default false */ get dnsCache() { return this._internals.dnsCache; } set dnsCache(value) { assert.any([is.object, is.boolean, is.undefined], value); if (value === true) { this._internals.dnsCache = getGlobalDnsCache(); } else if (value === false) { this._internals.dnsCache = undefined; } else { this._internals.dnsCache = value; } } /** User data. `context` is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged. @example ``` import got from 'got'; const instance = got.extend({ hooks: { beforeRequest: [ options => { if (!options.context || !options.context.token) { throw new Error('Token required'); } options.headers.token = options.context.token; } ] } }); const context = { token: 'secret' }; const response = await instance('https://httpbin.org/headers', {context}); // Let's see the headers console.log(response.body); ``` */ get context() { return this._internals.context; } set context(value) { assert.object(value); if (this._merging) { Object.assign(this._internals.context, value); } else { this._internals.context = { ...value }; } } /** Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially. */ get hooks() { return this._internals.hooks; } set hooks(value) { assert.object(value); // eslint-disable-next-line guard-for-in for (const knownHookEvent in value) { if (!(knownHookEvent in this._internals.hooks)) { throw new Error(`Unexpected hook event: ${knownHookEvent}`); } const typedKnownHookEvent = knownHookEvent; const hooks = value[typedKnownHookEvent]; assert.any([is.array, is.undefined], hooks); if (hooks) { for (const hook of hooks) { assert.function_(hook); } } if (this._merging) { if (hooks) { // @ts-expect-error FIXME this._internals.hooks[typedKnownHookEvent].push(...hooks); } } else { if (!hooks) { throw new Error(`Missing hook event: ${knownHookEvent}`); } // @ts-expect-error FIXME this._internals.hooks[knownHookEvent] = [...hooks]; } } } /** Defines if redirect responses should be followed automatically. Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`. This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). You can optionally turn on this behavior also for other redirect codes - see `methodRewriting`. @default true */ get followRedirect() { return this._internals.followRedirect; } set followRedirect(value) { assert.boolean(value); this._internals.followRedirect = value; } get followRedirects() { throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.'); } set followRedirects(_value) { throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.'); } /** If exceeded, the request will be aborted and a `MaxRedirectsError` will be thrown. @default 10 */ get maxRedirects() { return this._internals.maxRedirects; } set maxRedirects(value) { assert.number(value); this._internals.maxRedirects = value; } /** A cache adapter instance for storing cached response data. @default false */ get cache() { return this._internals.cache; } set cache(value) { assert.any([is.object, is.string, is.boolean, is.undefined], value); if (value === true) { this._internals.cache = globalCache; } else if (value === false) { this._internals.cache = undefined; } else { this._internals.cache = value; } } /** Determines if a `got.HTTPError` is thrown for unsuccessful responses. If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses. @default true */ get throwHttpErrors() { return this._internals.throwHttpErrors; } set throwHttpErrors(value) { assert.boolean(value); this._internals.throwHttpErrors = value; } get username() { const url = this._internals.url; const value = url ? url.username : this._internals.username; return decodeURIComponent(value); } set username(value) { assert.string(value); const url = this._internals.url; const fixedValue = encodeURIComponent(value); if (url) { url.username = fixedValue; } else { this._internals.username = fixedValue; } } get password() { const url = this._internals.url; const value = url ? url.password : this._internals.password; return decodeURIComponent(value); } set password(value) { assert.string(value); const url = this._internals.url; const fixedValue = encodeURIComponent(value); if (url) { url.password = fixedValue; } else { this._internals.password = fixedValue; } } /** If set to `true`, Got will additionally accept HTTP2 requests. It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol. __Note__: This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy. __Note__: Overriding `options.request` will disable HTTP2 support. @default false @example ``` import got from 'got'; const {headers} = await got('https://nghttp2.org/httpbin/anything', {http2: true}); console.log(headers.via); //=> '2 nghttpx' ``` */ get http2() { return this._internals.http2; } set http2(value) { assert.boolean(value); this._internals.http2 = value; } /** Set this to `true` to allow sending body for the `GET` method. However, the [HTTP/2 specification](https://tools.ietf.org/html/rfc7540#section-8.1.3) says that `An HTTP GET request includes request header fields and no payload body`, therefore when using the HTTP/2 protocol this option will have no effect. This option is only meant to interact with non-compliant servers when you have no other choice. __Note__: The [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) doesn't specify any particular behavior for the GET method having a payload, therefore __it's considered an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern)__. @default false */ get allowGetBody() { return this._internals.allowGetBody; } set allowGetBody(value) { assert.boolean(value); this._internals.allowGetBody = value; } /** Request headers. Existing headers will be overwritten. Headers set to `undefined` will be omitted. @default {} */ get headers() { return this._internals.headers; } set headers(value) { assert.plainObject(value); if (this._merging) { Object.assign(this._internals.headers, lowercaseKeys(value)); } else { this._internals.headers = lowercaseKeys(value); } } /** Specifies if the HTTP request method should be [rewritten as `GET`](https://tools.ietf.org/html/rfc7231#section-6.4) on redirects. As the [specification](https://tools.ietf.org/html/rfc7231#section-6.4) prefers to rewrite the HTTP method only on `303` responses, this is Got's default behavior. Setting `methodRewriting` to `true` will also rewrite `301` and `302` responses, as allowed by the spec. This is the behavior followed by `curl` and browsers. __Note__: Got never performs method rewriting on `307` and `308` responses, as this is [explicitly prohibited by the specification](https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7). @default false */ get methodRewriting() { return this._internals.methodRewriting; } set methodRewriting(value) { assert.boolean(value); this._internals.methodRewriting = value; } /** Indicates which DNS record family to use. Values: - `undefined`: IPv4 (if present) or IPv6 - `4`: Only IPv4 - `6`: Only IPv6 @default undefined */ get dnsLookupIpVersion() { return this._internals.dnsLookupIpVersion; } set dnsLookupIpVersion(value) { if (value !== undefined && value !== 4 && value !== 6) { throw new TypeError(`Invalid DNS lookup IP version: ${value}`); } this._internals.dnsLookupIpVersion = value; } /** A function used to parse JSON responses. @example ``` import got from 'got'; import Bourne from '@hapi/bourne'; const parsed = await got('https://example.com', { parseJson: text => Bourne.parse(text) }).json(); console.log(parsed); ``` */ get parseJson() { return this._internals.parseJson; } set parseJson(value) { assert.function_(value); this._internals.parseJson = value; } /** A function used to stringify the body of JSON requests. @example ``` import got from 'got'; await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (key.startsWith('_')) { return; } return value; }), json: { some: 'payload', _ignoreMe: 1234 } }); ``` @example ``` import got from 'got'; await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (typeof value === 'number') { return value.toString(); } return value; }), json: { some: 'payload', number: 1 } }); ``` */ get stringifyJson() { return this._internals.stringifyJson; } set stringifyJson(value) { assert.function_(value); this._internals.stringifyJson = value; } /** An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes. Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1). The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value. The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry). By default, it retries *only* on the specified methods, status codes, and on these network errors: - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached. - `ECONNRESET`: Connection was forcibly closed by a peer. - `EADDRINUSE`: Could not bind to any free port. - `ECONNREFUSED`: Connection was refused by the server. - `EPIPE`: The remote side of the stream being written has been closed. - `ENOTFOUND`: Couldn't resolve the hostname to an IP address. - `ENETUNREACH`: No internet connection. - `EAI_AGAIN`: DNS lookup timed out. __Note__: If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. __Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request. */ get retry() { return this._internals.retry; } set retry(value) { assert.plainObject(value); assert.any([is.function_, is.undefined], value.calculateDelay); assert.any([is.number, is.undefined], value.maxRetryAfter); assert.any([is.number, is.undefined], value.limit); assert.any([is.array, is.undefined], value.methods); assert.any([is.array, is.undefined], value.statusCodes); assert.any([is.array, is.undefined], value.errorCodes); assert.any([is.number, is.undefined], value.noise); if (value.noise && Math.abs(value.noise) > 100) { throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`); } for (const key in value) { if (!(key in this._internals.retry)) { throw new Error(`Unexpected retry option: ${key}`); } } if (this._merging) { Object.assign(this._internals.retry, value); } else { this._internals.retry = { ...value }; } const { retry } = this._internals; retry.methods = [...new Set(retry.methods.map(method => method.toUpperCase()))]; retry.statusCodes = [...new Set(retry.statusCodes)]; retry.errorCodes = [...new Set(retry.errorCodes)]; } /** From `http.RequestOptions`. The IP address used to send the request from. */ get localAddress() { return this._internals.localAddress; } set localAddress(value) { assert.any([is.string, is.undefined], value); this._internals.localAddress = value; } /** The HTTP method used to make the request. @default 'GET' */ get method() { return this._internals.method; } set method(value) { assert.string(value); this._internals.method = value.toUpperCase(); } get createConnection() { return this._internals.createConnection; } set createConnection(value) { assert.any([is.function_, is.undefined], value); this._internals.createConnection = value; } /** From `http-cache-semantics` @default {} */ get cacheOptions() { return this._internals.cacheOptions; } set cacheOptions(value) { assert.plainObject(value); assert.any([is.boolean, is.undefined], value.shared); assert.any([is.number, is.undefined], value.cacheHeuristic); assert.any([is.number, is.undefined], value.immutableMinTimeToLive); assert.any([is.boolean, is.undefined], value.ignoreCargoCult); for (const key in value) { if (!(key in this._internals.cacheOptions)) { throw new Error(`Cache option \`${key}\` does not exist`); } } if (this._merging) { Object.assign(this._internals.cacheOptions, value); } else { this._internals.cacheOptions = { ...value }; } } /** Options for the advanced HTTPS API. */ get https() { return this._internals.https; } set https(value) { assert.plainObject(value); assert.any([is.boolean, is.undefined], value.rejectUnauthorized); assert.any([is.function_, is.undefined], value.checkServerIdentity); assert.any([is.string, is.object, is.array, is.undefined], value.certificateAuthority); assert.any([is.string, is.object, is.array, is.undefined], value.key); assert.any([is.string, is.object, is.array, is.undefined], value.certificate); assert.any([is.string, is.undefined], value.passphrase); assert.any([is.string, is.buffer, is.array, is.undefined], value.pfx); assert.any([is.array, is.undefined], value.alpnProtocols); assert.any([is.string, is.undefined], value.ciphers); assert.any([is.string, is.buffer, is.undefined], value.dhparam); assert.any([is.string, is.undefined], value.signatureAlgorithms); assert.any([is.string, is.undefined], value.minVersion); assert.any([is.string, is.undefined], value.maxVersion); assert.any([is.boolean, is.undefined], value.honorCipherOrder); assert.any([is.number, is.undefined], value.tlsSessionLifetime); assert.any([is.string, is.undefined], value.ecdhCurve); assert.any([is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists); for (const key in value) { if (!(key in this._internals.https)) { throw new Error(`HTTPS option \`${key}\` does not exist`); } } if (this._merging) { Object.assign(this._internals.https, value); } else { this._internals.https = { ...value }; } } /** [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data. To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set `responseType` to `buffer` instead. Don't set this option to `null`. __Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`. @default 'utf-8' */ get encoding() { return this._internals.encoding; } set encoding(value) { if (value === null) { throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead'); } assert.any([is.string, is.undefined], value); this._internals.encoding = value; } /** When set to `true` the promise will return the Response body instead of the Response object. @default false */ get resolveBodyOnly() { return this._internals.resolveBodyOnly; } set resolveBodyOnly(value) { assert.boolean(value); this._internals.resolveBodyOnly = value; } /** Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.stream(url, options?)`. @default false */ get isStream() { return this._internals.isStream; } set isStream(value) { assert.boolean(value); this._internals.isStream = value; } /** The parsing method. The promise also has `.text()`, `.json()` and `.buffer()` methods which return another Got promise for the parsed body. It's like setting the options to `{responseType: 'json', resolveBodyOnly: true}` but without affecting the main Got promise. __Note__: When using streams, this option is ignored. @example ``` const responsePromise = got(url); const bufferPromise = responsePromise.buffer(); const jsonPromise = responsePromise.json(); const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]); // `response` is an instance of Got Response // `buffer` is an instance of Buffer // `json` is an object ``` @example ``` // This const body = await got(url).json(); // is semantically the same as this const body = await got(url, {responseType: 'json', resolveBodyOnly: true}); ``` */ get responseType() { return this._internals.responseType; } set responseType(value) { if (value === undefined) { this._internals.responseType = 'text'; return; } if (value !== 'text' && value !== 'buffer' && value !== 'json') { throw new Error(`Invalid \`responseType\` option: ${value}`); } this._internals.responseType = value; } get pagination() { return this._internals.pagination; } set pagination(value) { assert.object(value); if (this._merging) { Object.assign(this._internals.pagination, value); } else { this._internals.pagination = value; } } get auth() { throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.'); } set auth(_value) { throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.'); } get setHost() { return this._internals.setHost; } set setHost(value) { assert.boolean(value); this._internals.setHost = value; } get maxHeaderSize() { return this._internals.maxHeaderSize; } set maxHeaderSize(value) { assert.any([is.number, is.undefined], value); this._internals.maxHeaderSize = value; } get enableUnixSockets() { return this._internals.enableUnixSockets; } set enableUnixSockets(value) { assert.boolean(value); this._internals.enableUnixSockets = value; } // eslint-disable-next-line @typescript-eslint/naming-convention toJSON() { return { ...this._internals }; } [Symbol.for('nodejs.util.inspect.custom')](_depth, options) { return inspect(this._internals, options); } createNativeRequestOptions() { const internals = this._internals; const url = internals.url; let agent; if (url.protocol === 'https:') { agent = internals.http2 ? internals.agent : internals.agent.https; } else { agent = internals.agent.http; } const { https } = internals; let { pfx } = https; if (is.array(pfx) && is.plainObject(pfx[0])) { pfx = pfx.map(object => ({ buf: object.buffer, passphrase: object.passphrase, })); } return { ...internals.cacheOptions, ...this._unixOptions, // HTTPS options // eslint-disable-next-line @typescript-eslint/naming-convention ALPNProtocols: https.alpnProtocols, ca: https.certificateAuthority, cert: https.certificate, key: https.key, passphrase: https.passphrase, pfx: https.pfx, rejectUnauthorized: https.rejectUnauthorized, checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity, ciphers: https.ciphers, honorCipherOrder: https.honorCipherOrder, minVersion: https.minVersion, maxVersion: https.maxVersion, sigalgs: https.signatureAlgorithms, sessionTimeout: https.tlsSessionLifetime, dhparam: https.dhparam, ecdhCurve: https.ecdhCurve, crl: https.certificateRevocationLists, // HTTP options lookup: internals.dnsLookup ?? internals.dnsCache?.lookup, family: internals.dnsLookupIpVersion, agent, setHost: internals.setHost, method: internals.method, maxHeaderSize: internals.maxHeaderSize, localAddress: internals.localAddress, headers: internals.headers, createConnection: internals.createConnection, timeout: internals.http2 ? getHttp2TimeoutOption(internals) : undefined, // HTTP/2 options h2session: internals.h2session, }; } getRequestFunction() { const url = this._internals.url; const { request } = this._internals; if (!request && url) { return this.getFallbackRequestFunction(); } return request; } getFallbackRequestFunction() { const url = this._internals.url; if (!url) { return; } if (url.protocol === 'https:') { if (this._internals.http2) { if (major < 15 || (major === 15 && minor < 10)) { const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above'); error.code = 'EUNSUPPORTED'; throw error; } return http2wrapper.auto; } return https.request; } return http.request; } freeze() { const options = this._internals; Object.freeze(options); Object.freeze(options.hooks); Object.freeze(options.hooks.afterResponse); Object.freeze(options.hooks.beforeError); Object.freeze(options.hooks.beforeRedirect); Object.freeze(options.hooks.beforeRequest); Object.freeze(options.hooks.beforeRetry); Object.freeze(options.hooks.init); Object.freeze(options.https); Object.freeze(options.cacheOptions); Object.freeze(options.agent); Object.freeze(options.headers); Object.freeze(options.timeout); Object.freeze(options.retry); Object.freeze(options.retry.errorCodes); Object.freeze(options.retry.methods); Object.freeze(options.retry.statusCodes); } }