| 1 | import utils from '../utils.js';
|
|---|
| 2 | import settle from '../core/settle.js';
|
|---|
| 3 | import buildFullPath from '../core/buildFullPath.js';
|
|---|
| 4 | import buildURL from '../helpers/buildURL.js';
|
|---|
| 5 | import { getProxyForUrl } from 'proxy-from-env';
|
|---|
| 6 | import HttpsProxyAgent from 'https-proxy-agent';
|
|---|
| 7 | import http from 'http';
|
|---|
| 8 | import https from 'https';
|
|---|
| 9 | import http2 from 'http2';
|
|---|
| 10 | import util from 'util';
|
|---|
| 11 | import { resolve as resolvePath } from 'path';
|
|---|
| 12 | import followRedirects from 'follow-redirects';
|
|---|
| 13 | import zlib from 'zlib';
|
|---|
| 14 | import { VERSION } from '../env/data.js';
|
|---|
| 15 | import transitionalDefaults from '../defaults/transitional.js';
|
|---|
| 16 | import AxiosError from '../core/AxiosError.js';
|
|---|
| 17 | import CanceledError from '../cancel/CanceledError.js';
|
|---|
| 18 | import platform from '../platform/index.js';
|
|---|
| 19 | import fromDataURI from '../helpers/fromDataURI.js';
|
|---|
| 20 | import stream from 'stream';
|
|---|
| 21 | import AxiosHeaders from '../core/AxiosHeaders.js';
|
|---|
| 22 | import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
|
|---|
| 23 | import { EventEmitter } from 'events';
|
|---|
| 24 | import formDataToStream from '../helpers/formDataToStream.js';
|
|---|
| 25 | import readBlob from '../helpers/readBlob.js';
|
|---|
| 26 | import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
|
|---|
| 27 | import callbackify from '../helpers/callbackify.js';
|
|---|
| 28 | import shouldBypassProxy from '../helpers/shouldBypassProxy.js';
|
|---|
| 29 | import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
|
|---|
| 30 | import {
|
|---|
| 31 | progressEventReducer,
|
|---|
| 32 | progressEventDecorator,
|
|---|
| 33 | asyncDecorator,
|
|---|
| 34 | } from '../helpers/progressEventReducer.js';
|
|---|
| 35 | import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
|
|---|
| 36 |
|
|---|
| 37 | const zlibOptions = {
|
|---|
| 38 | flush: zlib.constants.Z_SYNC_FLUSH,
|
|---|
| 39 | finishFlush: zlib.constants.Z_SYNC_FLUSH,
|
|---|
| 40 | };
|
|---|
| 41 |
|
|---|
| 42 | const brotliOptions = {
|
|---|
| 43 | flush: zlib.constants.BROTLI_OPERATION_FLUSH,
|
|---|
| 44 | finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
|
|---|
| 45 | };
|
|---|
| 46 |
|
|---|
| 47 | const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
|
|---|
| 48 |
|
|---|
| 49 | const { http: httpFollow, https: httpsFollow } = followRedirects;
|
|---|
| 50 |
|
|---|
| 51 | const isHttps = /https:?/;
|
|---|
| 52 | const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
|
|---|
| 53 |
|
|---|
| 54 | function setFormDataHeaders(headers, formHeaders, policy) {
|
|---|
| 55 | if (policy !== 'content-only') {
|
|---|
| 56 | headers.set(formHeaders);
|
|---|
| 57 | return;
|
|---|
| 58 | }
|
|---|
| 59 |
|
|---|
| 60 | Object.entries(formHeaders).forEach(([key, val]) => {
|
|---|
| 61 | if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
|
|---|
| 62 | headers.set(key, val);
|
|---|
| 63 | }
|
|---|
| 64 | });
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | // Symbols used to bind a single 'error' listener to a pooled socket and track
|
|---|
| 68 | // the request currently owning that socket across keep-alive reuse (issue #10780).
|
|---|
| 69 | const kAxiosSocketListener = Symbol('axios.http.socketListener');
|
|---|
| 70 | const kAxiosCurrentReq = Symbol('axios.http.currentReq');
|
|---|
| 71 |
|
|---|
| 72 | // Tags HttpsProxyAgent instances installed by setProxy() so the redirect path
|
|---|
| 73 | // can strip them without clobbering a user-supplied agent that happens to be
|
|---|
| 74 | // an HttpsProxyAgent.
|
|---|
| 75 | const kAxiosInstalledTunnel = Symbol('axios.http.installedTunnel');
|
|---|
| 76 |
|
|---|
| 77 | // Cache of CONNECT-tunneling agents keyed by proxy config so repeat requests
|
|---|
| 78 | // through the same proxy reuse a single agent (and its socket pool). The
|
|---|
| 79 | // keyspace is bounded by the set of distinct proxy configs the process uses,
|
|---|
| 80 | // so unbounded growth is not a concern in practice.
|
|---|
| 81 | const tunnelingAgentCache = new Map();
|
|---|
| 82 | const tunnelingAgentCacheUser = new WeakMap();
|
|---|
| 83 |
|
|---|
| 84 | function getTunnelingAgent(agentOptions, userHttpsAgent) {
|
|---|
| 85 | const key =
|
|---|
| 86 | agentOptions.protocol +
|
|---|
| 87 | '//' +
|
|---|
| 88 | agentOptions.hostname +
|
|---|
| 89 | ':' +
|
|---|
| 90 | (agentOptions.port || '') +
|
|---|
| 91 | '#' +
|
|---|
| 92 | (agentOptions.auth || '');
|
|---|
| 93 | const cache = userHttpsAgent
|
|---|
| 94 | ? (tunnelingAgentCacheUser.get(userHttpsAgent) ||
|
|---|
| 95 | tunnelingAgentCacheUser.set(userHttpsAgent, new Map()).get(userHttpsAgent))
|
|---|
| 96 | : tunnelingAgentCache;
|
|---|
| 97 | let agent = cache.get(key);
|
|---|
| 98 | if (agent) return agent;
|
|---|
| 99 | // Forward the user's TLS options (custom CA, rejectUnauthorized, client cert,
|
|---|
| 100 | // etc.) into the tunneling agent so they apply to the origin TLS upgrade
|
|---|
| 101 | // performed after CONNECT. Our proxy fields take precedence on conflict.
|
|---|
| 102 | const merged = userHttpsAgent && userHttpsAgent.options
|
|---|
| 103 | ? { ...userHttpsAgent.options, ...agentOptions }
|
|---|
| 104 | : agentOptions;
|
|---|
| 105 | agent = new HttpsProxyAgent(merged);
|
|---|
| 106 | agent[kAxiosInstalledTunnel] = true;
|
|---|
| 107 | cache.set(key, agent);
|
|---|
| 108 | return agent;
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | const supportedProtocols = platform.protocols.map((protocol) => {
|
|---|
| 112 | return protocol + ':';
|
|---|
| 113 | });
|
|---|
| 114 |
|
|---|
| 115 | // Node's WHATWG URL parser returns `username` and `password` percent-encoded.
|
|---|
| 116 | // Decode before composing the `auth` option so credentials such as
|
|---|
| 117 | // `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
|
|---|
| 118 | // original value for malformed input so a bad encoding never throws.
|
|---|
| 119 | const decodeURIComponentSafe = (value) => {
|
|---|
| 120 | if (!utils.isString(value)) {
|
|---|
| 121 | return value;
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | try {
|
|---|
| 125 | return decodeURIComponent(value);
|
|---|
| 126 | } catch (error) {
|
|---|
| 127 | return value;
|
|---|
| 128 | }
|
|---|
| 129 | };
|
|---|
| 130 |
|
|---|
| 131 | const flushOnFinish = (stream, [throttled, flush]) => {
|
|---|
| 132 | stream.on('end', flush).on('error', flush);
|
|---|
| 133 |
|
|---|
| 134 | return throttled;
|
|---|
| 135 | };
|
|---|
| 136 |
|
|---|
| 137 | class Http2Sessions {
|
|---|
| 138 | constructor() {
|
|---|
| 139 | this.sessions = Object.create(null);
|
|---|
| 140 | }
|
|---|
| 141 |
|
|---|
| 142 | getSession(authority, options) {
|
|---|
| 143 | options = Object.assign(
|
|---|
| 144 | {
|
|---|
| 145 | sessionTimeout: 1000,
|
|---|
| 146 | },
|
|---|
| 147 | options
|
|---|
| 148 | );
|
|---|
| 149 |
|
|---|
| 150 | let authoritySessions = this.sessions[authority];
|
|---|
| 151 |
|
|---|
| 152 | if (authoritySessions) {
|
|---|
| 153 | let len = authoritySessions.length;
|
|---|
| 154 |
|
|---|
| 155 | for (let i = 0; i < len; i++) {
|
|---|
| 156 | const [sessionHandle, sessionOptions] = authoritySessions[i];
|
|---|
| 157 | if (
|
|---|
| 158 | !sessionHandle.destroyed &&
|
|---|
| 159 | !sessionHandle.closed &&
|
|---|
| 160 | util.isDeepStrictEqual(sessionOptions, options)
|
|---|
| 161 | ) {
|
|---|
| 162 | return sessionHandle;
|
|---|
| 163 | }
|
|---|
| 164 | }
|
|---|
| 165 | }
|
|---|
| 166 |
|
|---|
| 167 | const session = http2.connect(authority, options);
|
|---|
| 168 |
|
|---|
| 169 | let removed;
|
|---|
| 170 |
|
|---|
| 171 | const removeSession = () => {
|
|---|
| 172 | if (removed) {
|
|---|
| 173 | return;
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | removed = true;
|
|---|
| 177 |
|
|---|
| 178 | let entries = authoritySessions,
|
|---|
| 179 | len = entries.length,
|
|---|
| 180 | i = len;
|
|---|
| 181 |
|
|---|
| 182 | while (i--) {
|
|---|
| 183 | if (entries[i][0] === session) {
|
|---|
| 184 | if (len === 1) {
|
|---|
| 185 | delete this.sessions[authority];
|
|---|
| 186 | } else {
|
|---|
| 187 | entries.splice(i, 1);
|
|---|
| 188 | }
|
|---|
| 189 | if (!session.closed) {
|
|---|
| 190 | session.close();
|
|---|
| 191 | }
|
|---|
| 192 | return;
|
|---|
| 193 | }
|
|---|
| 194 | }
|
|---|
| 195 | };
|
|---|
| 196 |
|
|---|
| 197 | const originalRequestFn = session.request;
|
|---|
| 198 |
|
|---|
| 199 | const { sessionTimeout } = options;
|
|---|
| 200 |
|
|---|
| 201 | if (sessionTimeout != null) {
|
|---|
| 202 | let timer;
|
|---|
| 203 | let streamsCount = 0;
|
|---|
| 204 |
|
|---|
| 205 | session.request = function () {
|
|---|
| 206 | const stream = originalRequestFn.apply(this, arguments);
|
|---|
| 207 |
|
|---|
| 208 | streamsCount++;
|
|---|
| 209 |
|
|---|
| 210 | if (timer) {
|
|---|
| 211 | clearTimeout(timer);
|
|---|
| 212 | timer = null;
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | stream.once('close', () => {
|
|---|
| 216 | if (!--streamsCount) {
|
|---|
| 217 | timer = setTimeout(() => {
|
|---|
| 218 | timer = null;
|
|---|
| 219 | removeSession();
|
|---|
| 220 | }, sessionTimeout);
|
|---|
| 221 | }
|
|---|
| 222 | });
|
|---|
| 223 |
|
|---|
| 224 | return stream;
|
|---|
| 225 | };
|
|---|
| 226 | }
|
|---|
| 227 |
|
|---|
| 228 | session.once('close', removeSession);
|
|---|
| 229 |
|
|---|
| 230 | let entry = [session, options];
|
|---|
| 231 |
|
|---|
| 232 | authoritySessions
|
|---|
| 233 | ? authoritySessions.push(entry)
|
|---|
| 234 | : (authoritySessions = this.sessions[authority] = [entry]);
|
|---|
| 235 |
|
|---|
| 236 | return session;
|
|---|
| 237 | }
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | const http2Sessions = new Http2Sessions();
|
|---|
| 241 |
|
|---|
| 242 | /**
|
|---|
| 243 | * If the proxy or config beforeRedirects functions are defined, call them with the options
|
|---|
| 244 | * object.
|
|---|
| 245 | *
|
|---|
| 246 | * @param {Object<string, any>} options - The options object that was passed to the request.
|
|---|
| 247 | *
|
|---|
| 248 | * @returns {Object<string, any>}
|
|---|
| 249 | */
|
|---|
| 250 | function dispatchBeforeRedirect(options, responseDetails, requestDetails) {
|
|---|
| 251 | if (options.beforeRedirects.proxy) {
|
|---|
| 252 | options.beforeRedirects.proxy(options);
|
|---|
| 253 | }
|
|---|
| 254 | if (options.beforeRedirects.config) {
|
|---|
| 255 | options.beforeRedirects.config(options, responseDetails, requestDetails);
|
|---|
| 256 | }
|
|---|
| 257 | }
|
|---|
| 258 |
|
|---|
| 259 | /**
|
|---|
| 260 | * If the proxy or config afterRedirects functions are defined, call them with the options
|
|---|
| 261 | *
|
|---|
| 262 | * @param {http.ClientRequestArgs} options
|
|---|
| 263 | * @param {AxiosProxyConfig} configProxy configuration from Axios options object
|
|---|
| 264 | * @param {string} location
|
|---|
| 265 | *
|
|---|
| 266 | * @returns {http.ClientRequestArgs}
|
|---|
| 267 | */
|
|---|
| 268 | function setProxy(options, configProxy, location, isRedirect, configHttpsAgent) {
|
|---|
| 269 | let proxy = configProxy;
|
|---|
| 270 | if (!proxy && proxy !== false) {
|
|---|
| 271 | const proxyUrl = getProxyForUrl(location);
|
|---|
| 272 | if (proxyUrl) {
|
|---|
| 273 | if (!shouldBypassProxy(location)) {
|
|---|
| 274 | proxy = new URL(proxyUrl);
|
|---|
| 275 | }
|
|---|
| 276 | }
|
|---|
| 277 | }
|
|---|
| 278 | // On redirect re-invocation, strip any stale Proxy-Authorization header carried
|
|---|
| 279 | // over from the prior request (e.g. new target no longer uses a proxy, or uses
|
|---|
| 280 | // a different proxy). Skip on the initial request so user-supplied headers are
|
|---|
| 281 | // preserved. Header names are case-insensitive, so remove every case variant.
|
|---|
| 282 | if (isRedirect && options.headers) {
|
|---|
| 283 | for (const name of Object.keys(options.headers)) {
|
|---|
| 284 | if (name.toLowerCase() === 'proxy-authorization') {
|
|---|
| 285 | delete options.headers[name];
|
|---|
| 286 | }
|
|---|
| 287 | }
|
|---|
| 288 | }
|
|---|
| 289 | // Strip any tunneling agent we installed for the previous hop so a redirect
|
|---|
| 290 | // that drops the proxy or crosses an HTTPS↔HTTP boundary doesn't reuse a
|
|---|
| 291 | // stale one. Match on our Symbol marker so a user-supplied HttpsProxyAgent
|
|---|
| 292 | // (which won't carry the marker) is left alone.
|
|---|
| 293 | if (isRedirect && options.agent && options.agent[kAxiosInstalledTunnel]) {
|
|---|
| 294 | options.agent = undefined;
|
|---|
| 295 | }
|
|---|
| 296 | if (proxy) {
|
|---|
| 297 | // Read proxy fields without traversing the prototype chain. URL instances expose
|
|---|
| 298 | // username/password/hostname/host/port/protocol via getters on URL.prototype (so
|
|---|
| 299 | // direct reads are shielded), but plain object proxies — and the `auth` field
|
|---|
| 300 | // (which URL does not expose) — must be guarded so a polluted Object.prototype
|
|---|
| 301 | // (e.g. Object.prototype.auth = { username, password }) cannot inject
|
|---|
| 302 | // attacker-controlled credentials into the Proxy-Authorization header or
|
|---|
| 303 | // redirect proxying to an attacker-controlled host.
|
|---|
| 304 | const isProxyURL = proxy instanceof URL;
|
|---|
| 305 | const readProxyField = (key) =>
|
|---|
| 306 | isProxyURL || utils.hasOwnProp(proxy, key) ? proxy[key] : undefined;
|
|---|
| 307 |
|
|---|
| 308 | const proxyUsername = readProxyField('username');
|
|---|
| 309 | const proxyPassword = readProxyField('password');
|
|---|
| 310 | let proxyAuth = utils.hasOwnProp(proxy, 'auth') ? proxy.auth : undefined;
|
|---|
| 311 |
|
|---|
| 312 | // Basic proxy authorization
|
|---|
| 313 | if (proxyUsername) {
|
|---|
| 314 | proxyAuth = (proxyUsername || '') + ':' + (proxyPassword || '');
|
|---|
| 315 | }
|
|---|
| 316 |
|
|---|
| 317 | if (proxyAuth) {
|
|---|
| 318 | // Support proxy auth object form. Read sub-fields via own-prop checks so a
|
|---|
| 319 | // plain object inheriting from polluted Object.prototype cannot leak creds.
|
|---|
| 320 | const authIsObject = typeof proxyAuth === 'object';
|
|---|
| 321 | const authUsername =
|
|---|
| 322 | authIsObject && utils.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined;
|
|---|
| 323 | const authPassword =
|
|---|
| 324 | authIsObject && utils.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined;
|
|---|
| 325 | const validProxyAuth = Boolean(authUsername || authPassword);
|
|---|
| 326 |
|
|---|
| 327 | if (validProxyAuth) {
|
|---|
| 328 | proxyAuth = (authUsername || '') + ':' + (authPassword || '');
|
|---|
| 329 | } else if (authIsObject) {
|
|---|
| 330 | throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
|
|---|
| 331 | }
|
|---|
| 332 | }
|
|---|
| 333 |
|
|---|
| 334 | const targetIsHttps = isHttps.test(options.protocol);
|
|---|
| 335 |
|
|---|
| 336 | if (targetIsHttps) {
|
|---|
| 337 | // CONNECT-tunneling path for HTTPS targets. Preserves end-to-end TLS to
|
|---|
| 338 | // the origin so the proxy cannot inspect the URL, headers, or body — the
|
|---|
| 339 | // behavior already promised by THREATMODEL.md (T-R9). HttpsProxyAgent
|
|---|
| 340 | // sends Proxy-Authorization on the CONNECT request only, never on the
|
|---|
| 341 | // wrapped TLS request, which is why we don't stamp it onto
|
|---|
| 342 | // options.headers here. If the user already supplied an HttpsProxyAgent,
|
|---|
| 343 | // they own tunneling end-to-end and we leave them alone; otherwise we
|
|---|
| 344 | // install our own tunneling agent and forward their TLS options (if any)
|
|---|
| 345 | // so a custom httpsAgent for cert pinning / rejectUnauthorized still
|
|---|
| 346 | // applies to the origin TLS upgrade.
|
|---|
| 347 | if (!(configHttpsAgent instanceof HttpsProxyAgent)) {
|
|---|
| 348 | const proxyHost = readProxyField('hostname') || readProxyField('host');
|
|---|
| 349 | const proxyPort = readProxyField('port');
|
|---|
| 350 | const rawProxyProtocol = readProxyField('protocol');
|
|---|
| 351 | const normalizedProtocol = rawProxyProtocol
|
|---|
| 352 | ? rawProxyProtocol.includes(':')
|
|---|
| 353 | ? rawProxyProtocol
|
|---|
| 354 | : `${rawProxyProtocol}:`
|
|---|
| 355 | : 'http:';
|
|---|
| 356 | // Bracket IPv6 literals for URL parsing; URL.hostname strips the
|
|---|
| 357 | // brackets again on read so the agent receives the raw form.
|
|---|
| 358 | const proxyHostForURL =
|
|---|
| 359 | proxyHost && proxyHost.includes(':') && !proxyHost.startsWith('[')
|
|---|
| 360 | ? `[${proxyHost}]`
|
|---|
| 361 | : proxyHost;
|
|---|
| 362 | const proxyURL = new URL(
|
|---|
| 363 | `${normalizedProtocol}//${proxyHostForURL}${proxyPort ? ':' + proxyPort : ''}`
|
|---|
| 364 | );
|
|---|
| 365 | const agentOptions = {
|
|---|
| 366 | protocol: proxyURL.protocol,
|
|---|
| 367 | hostname: proxyURL.hostname.replace(/^\[|\]$/g, ''),
|
|---|
| 368 | port: proxyURL.port,
|
|---|
| 369 | auth: proxyAuth && typeof proxyAuth === 'string' ? proxyAuth : undefined,
|
|---|
| 370 | };
|
|---|
| 371 | if (proxyURL.protocol === 'https:') {
|
|---|
| 372 | agentOptions.ALPNProtocols = ['http/1.1'];
|
|---|
| 373 | }
|
|---|
| 374 | const tunnelingAgent = getTunnelingAgent(agentOptions, configHttpsAgent);
|
|---|
| 375 | // Set both: `options.agent` is consumed by the native https.request path
|
|---|
| 376 | // (config.maxRedirects === 0); `options.agents.https` is consumed by
|
|---|
| 377 | // follow-redirects, which ignores `options.agent` when `options.agents`
|
|---|
| 378 | // is present.
|
|---|
| 379 | options.agent = tunnelingAgent;
|
|---|
| 380 | if (options.agents) {
|
|---|
| 381 | options.agents.https = tunnelingAgent;
|
|---|
| 382 | }
|
|---|
| 383 | }
|
|---|
| 384 | } else {
|
|---|
| 385 | // Forward-proxy mode for plaintext HTTP targets. The request line carries
|
|---|
| 386 | // the absolute URL and the proxy sees everything — acceptable for plain
|
|---|
| 387 | // HTTP since the wire was already plaintext.
|
|---|
| 388 | if (proxyAuth) {
|
|---|
| 389 | const base64 = Buffer.from(proxyAuth, 'utf8').toString('base64');
|
|---|
| 390 | options.headers['Proxy-Authorization'] = 'Basic ' + base64;
|
|---|
| 391 | }
|
|---|
| 392 |
|
|---|
| 393 | // Preserve a user-supplied Host header (case-insensitive) so callers can override
|
|---|
| 394 | // the value forwarded to the proxy; otherwise default to the request URL's host.
|
|---|
| 395 | let hasUserHostHeader = false;
|
|---|
| 396 | for (const name of Object.keys(options.headers)) {
|
|---|
| 397 | if (name.toLowerCase() === 'host') {
|
|---|
| 398 | hasUserHostHeader = true;
|
|---|
| 399 | break;
|
|---|
| 400 | }
|
|---|
| 401 | }
|
|---|
| 402 | if (!hasUserHostHeader) {
|
|---|
| 403 | options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
|
|---|
| 404 | }
|
|---|
| 405 | const proxyHost = readProxyField('hostname') || readProxyField('host');
|
|---|
| 406 | options.hostname = proxyHost;
|
|---|
| 407 | // Replace 'host' since options is not a URL object
|
|---|
| 408 | options.host = proxyHost;
|
|---|
| 409 | options.port = readProxyField('port');
|
|---|
| 410 | options.path = location;
|
|---|
| 411 | const proxyProtocol = readProxyField('protocol');
|
|---|
| 412 | if (proxyProtocol) {
|
|---|
| 413 | options.protocol = proxyProtocol.includes(':') ? proxyProtocol : `${proxyProtocol}:`;
|
|---|
| 414 | }
|
|---|
| 415 | }
|
|---|
| 416 | }
|
|---|
| 417 |
|
|---|
| 418 | options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
|
|---|
| 419 | // Configure proxy for redirected request, passing the original config proxy to apply
|
|---|
| 420 | // the exact same logic as if the redirected request was performed by axios directly.
|
|---|
| 421 | setProxy(redirectOptions, configProxy, redirectOptions.href, true, configHttpsAgent);
|
|---|
| 422 | };
|
|---|
| 423 | }
|
|---|
| 424 |
|
|---|
| 425 | const isHttpAdapterSupported =
|
|---|
| 426 | typeof process !== 'undefined' && utils.kindOf(process) === 'process';
|
|---|
| 427 |
|
|---|
| 428 | // temporary hotfix
|
|---|
| 429 |
|
|---|
| 430 | const wrapAsync = (asyncExecutor) => {
|
|---|
| 431 | return new Promise((resolve, reject) => {
|
|---|
| 432 | let onDone;
|
|---|
| 433 | let isDone;
|
|---|
| 434 |
|
|---|
| 435 | const done = (value, isRejected) => {
|
|---|
| 436 | if (isDone) return;
|
|---|
| 437 | isDone = true;
|
|---|
| 438 | onDone && onDone(value, isRejected);
|
|---|
| 439 | };
|
|---|
| 440 |
|
|---|
| 441 | const _resolve = (value) => {
|
|---|
| 442 | done(value);
|
|---|
| 443 | resolve(value);
|
|---|
| 444 | };
|
|---|
| 445 |
|
|---|
| 446 | const _reject = (reason) => {
|
|---|
| 447 | done(reason, true);
|
|---|
| 448 | reject(reason);
|
|---|
| 449 | };
|
|---|
| 450 |
|
|---|
| 451 | asyncExecutor(_resolve, _reject, (onDoneHandler) => (onDone = onDoneHandler)).catch(_reject);
|
|---|
| 452 | });
|
|---|
| 453 | };
|
|---|
| 454 |
|
|---|
| 455 | const resolveFamily = ({ address, family }) => {
|
|---|
| 456 | if (!utils.isString(address)) {
|
|---|
| 457 | throw TypeError('address must be a string');
|
|---|
| 458 | }
|
|---|
| 459 | return {
|
|---|
| 460 | address,
|
|---|
| 461 | family: family || (address.indexOf('.') < 0 ? 6 : 4),
|
|---|
| 462 | };
|
|---|
| 463 | };
|
|---|
| 464 |
|
|---|
| 465 | const buildAddressEntry = (address, family) =>
|
|---|
| 466 | resolveFamily(utils.isObject(address) ? address : { address, family });
|
|---|
| 467 |
|
|---|
| 468 | const http2Transport = {
|
|---|
| 469 | request(options, cb) {
|
|---|
| 470 | const authority =
|
|---|
| 471 | options.protocol +
|
|---|
| 472 | '//' +
|
|---|
| 473 | options.hostname +
|
|---|
| 474 | ':' +
|
|---|
| 475 | (options.port || (options.protocol === 'https:' ? 443 : 80));
|
|---|
| 476 |
|
|---|
| 477 | const { http2Options, headers } = options;
|
|---|
| 478 |
|
|---|
| 479 | const session = http2Sessions.getSession(authority, http2Options);
|
|---|
| 480 |
|
|---|
| 481 | const { HTTP2_HEADER_SCHEME, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS } =
|
|---|
| 482 | http2.constants;
|
|---|
| 483 |
|
|---|
| 484 | const http2Headers = {
|
|---|
| 485 | [HTTP2_HEADER_SCHEME]: options.protocol.replace(':', ''),
|
|---|
| 486 | [HTTP2_HEADER_METHOD]: options.method,
|
|---|
| 487 | [HTTP2_HEADER_PATH]: options.path,
|
|---|
| 488 | };
|
|---|
| 489 |
|
|---|
| 490 | utils.forEach(headers, (header, name) => {
|
|---|
| 491 | name.charAt(0) !== ':' && (http2Headers[name] = header);
|
|---|
| 492 | });
|
|---|
| 493 |
|
|---|
| 494 | const req = session.request(http2Headers);
|
|---|
| 495 |
|
|---|
| 496 | req.once('response', (responseHeaders) => {
|
|---|
| 497 | const response = req; //duplex
|
|---|
| 498 |
|
|---|
| 499 | responseHeaders = Object.assign({}, responseHeaders);
|
|---|
| 500 |
|
|---|
| 501 | const status = responseHeaders[HTTP2_HEADER_STATUS];
|
|---|
| 502 |
|
|---|
| 503 | delete responseHeaders[HTTP2_HEADER_STATUS];
|
|---|
| 504 |
|
|---|
| 505 | response.headers = responseHeaders;
|
|---|
| 506 |
|
|---|
| 507 | response.statusCode = +status;
|
|---|
| 508 |
|
|---|
| 509 | cb(response);
|
|---|
| 510 | });
|
|---|
| 511 |
|
|---|
| 512 | return req;
|
|---|
| 513 | },
|
|---|
| 514 | };
|
|---|
| 515 |
|
|---|
| 516 | /*eslint consistent-return:0*/
|
|---|
| 517 | export default isHttpAdapterSupported &&
|
|---|
| 518 | function httpAdapter(config) {
|
|---|
| 519 | return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
|
|---|
| 520 | const own = (key) => (utils.hasOwnProp(config, key) ? config[key] : undefined);
|
|---|
| 521 | let data = own('data');
|
|---|
| 522 | let lookup = own('lookup');
|
|---|
| 523 | let family = own('family');
|
|---|
| 524 | let httpVersion = own('httpVersion');
|
|---|
| 525 | if (httpVersion === undefined) httpVersion = 1;
|
|---|
| 526 | let http2Options = own('http2Options');
|
|---|
| 527 | const responseType = own('responseType');
|
|---|
| 528 | const responseEncoding = own('responseEncoding');
|
|---|
| 529 | const method = config.method.toUpperCase();
|
|---|
| 530 | let isDone;
|
|---|
| 531 | let rejected = false;
|
|---|
| 532 | let req;
|
|---|
| 533 | let connectPhaseTimer;
|
|---|
| 534 |
|
|---|
| 535 | httpVersion = +httpVersion;
|
|---|
| 536 |
|
|---|
| 537 | if (Number.isNaN(httpVersion)) {
|
|---|
| 538 | throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`);
|
|---|
| 539 | }
|
|---|
| 540 |
|
|---|
| 541 | if (httpVersion !== 1 && httpVersion !== 2) {
|
|---|
| 542 | throw TypeError(`Unsupported protocol version '${httpVersion}'`);
|
|---|
| 543 | }
|
|---|
| 544 |
|
|---|
| 545 | const isHttp2 = httpVersion === 2;
|
|---|
| 546 |
|
|---|
| 547 | if (lookup) {
|
|---|
| 548 | const _lookup = callbackify(lookup, (value) => (utils.isArray(value) ? value : [value]));
|
|---|
| 549 | // hotfix to support opt.all option which is required for node 20.x
|
|---|
| 550 | lookup = (hostname, opt, cb) => {
|
|---|
| 551 | _lookup(hostname, opt, (err, arg0, arg1) => {
|
|---|
| 552 | if (err) {
|
|---|
| 553 | return cb(err);
|
|---|
| 554 | }
|
|---|
| 555 |
|
|---|
| 556 | const addresses = utils.isArray(arg0)
|
|---|
| 557 | ? arg0.map((addr) => buildAddressEntry(addr))
|
|---|
| 558 | : [buildAddressEntry(arg0, arg1)];
|
|---|
| 559 |
|
|---|
| 560 | opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
|
|---|
| 561 | });
|
|---|
| 562 | };
|
|---|
| 563 | }
|
|---|
| 564 |
|
|---|
| 565 | const abortEmitter = new EventEmitter();
|
|---|
| 566 |
|
|---|
| 567 | function abort(reason) {
|
|---|
| 568 | try {
|
|---|
| 569 | abortEmitter.emit(
|
|---|
| 570 | 'abort',
|
|---|
| 571 | !reason || reason.type ? new CanceledError(null, config, req) : reason
|
|---|
| 572 | );
|
|---|
| 573 | } catch (err) {
|
|---|
| 574 | console.warn('emit error', err);
|
|---|
| 575 | }
|
|---|
| 576 | }
|
|---|
| 577 |
|
|---|
| 578 | function clearConnectPhaseTimer() {
|
|---|
| 579 | if (connectPhaseTimer) {
|
|---|
| 580 | clearTimeout(connectPhaseTimer);
|
|---|
| 581 | connectPhaseTimer = null;
|
|---|
| 582 | }
|
|---|
| 583 | }
|
|---|
| 584 |
|
|---|
| 585 | function createTimeoutError() {
|
|---|
| 586 | let timeoutErrorMessage = config.timeout
|
|---|
| 587 | ? 'timeout of ' + config.timeout + 'ms exceeded'
|
|---|
| 588 | : 'timeout exceeded';
|
|---|
| 589 | const transitional = config.transitional || transitionalDefaults;
|
|---|
| 590 | if (config.timeoutErrorMessage) {
|
|---|
| 591 | timeoutErrorMessage = config.timeoutErrorMessage;
|
|---|
| 592 | }
|
|---|
| 593 | return new AxiosError(
|
|---|
| 594 | timeoutErrorMessage,
|
|---|
| 595 | transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
|---|
| 596 | config,
|
|---|
| 597 | req
|
|---|
| 598 | );
|
|---|
| 599 | }
|
|---|
| 600 |
|
|---|
| 601 | abortEmitter.once('abort', reject);
|
|---|
| 602 |
|
|---|
| 603 | const onFinished = () => {
|
|---|
| 604 | clearConnectPhaseTimer();
|
|---|
| 605 |
|
|---|
| 606 | if (config.cancelToken) {
|
|---|
| 607 | config.cancelToken.unsubscribe(abort);
|
|---|
| 608 | }
|
|---|
| 609 |
|
|---|
| 610 | if (config.signal) {
|
|---|
| 611 | config.signal.removeEventListener('abort', abort);
|
|---|
| 612 | }
|
|---|
| 613 |
|
|---|
| 614 | abortEmitter.removeAllListeners();
|
|---|
| 615 | };
|
|---|
| 616 |
|
|---|
| 617 | if (config.cancelToken || config.signal) {
|
|---|
| 618 | config.cancelToken && config.cancelToken.subscribe(abort);
|
|---|
| 619 | if (config.signal) {
|
|---|
| 620 | config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort);
|
|---|
| 621 | }
|
|---|
| 622 | }
|
|---|
| 623 |
|
|---|
| 624 | onDone((response, isRejected) => {
|
|---|
| 625 | isDone = true;
|
|---|
| 626 | clearConnectPhaseTimer();
|
|---|
| 627 |
|
|---|
| 628 | if (isRejected) {
|
|---|
| 629 | rejected = true;
|
|---|
| 630 | onFinished();
|
|---|
| 631 | return;
|
|---|
| 632 | }
|
|---|
| 633 |
|
|---|
| 634 | const { data } = response;
|
|---|
| 635 |
|
|---|
| 636 | if (data instanceof stream.Readable || data instanceof stream.Duplex) {
|
|---|
| 637 | const offListeners = stream.finished(data, () => {
|
|---|
| 638 | offListeners();
|
|---|
| 639 | onFinished();
|
|---|
| 640 | });
|
|---|
| 641 | } else {
|
|---|
| 642 | onFinished();
|
|---|
| 643 | }
|
|---|
| 644 | });
|
|---|
| 645 |
|
|---|
| 646 | // Parse url
|
|---|
| 647 | const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls);
|
|---|
| 648 | const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
|
|---|
| 649 | const protocol = parsed.protocol || supportedProtocols[0];
|
|---|
| 650 |
|
|---|
| 651 | if (protocol === 'data:') {
|
|---|
| 652 | // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set.
|
|---|
| 653 | if (config.maxContentLength > -1) {
|
|---|
| 654 | // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed.
|
|---|
| 655 | const dataUrl = String(config.url || fullPath || '');
|
|---|
| 656 | const estimated = estimateDataURLDecodedBytes(dataUrl);
|
|---|
| 657 |
|
|---|
| 658 | if (estimated > config.maxContentLength) {
|
|---|
| 659 | return reject(
|
|---|
| 660 | new AxiosError(
|
|---|
| 661 | 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
|
|---|
| 662 | AxiosError.ERR_BAD_RESPONSE,
|
|---|
| 663 | config
|
|---|
| 664 | )
|
|---|
| 665 | );
|
|---|
| 666 | }
|
|---|
| 667 | }
|
|---|
| 668 |
|
|---|
| 669 | let convertedData;
|
|---|
| 670 |
|
|---|
| 671 | if (method !== 'GET') {
|
|---|
| 672 | return settle(resolve, reject, {
|
|---|
| 673 | status: 405,
|
|---|
| 674 | statusText: 'method not allowed',
|
|---|
| 675 | headers: {},
|
|---|
| 676 | config,
|
|---|
| 677 | });
|
|---|
| 678 | }
|
|---|
| 679 |
|
|---|
| 680 | try {
|
|---|
| 681 | convertedData = fromDataURI(config.url, responseType === 'blob', {
|
|---|
| 682 | Blob: config.env && config.env.Blob,
|
|---|
| 683 | });
|
|---|
| 684 | } catch (err) {
|
|---|
| 685 | throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
|
|---|
| 686 | }
|
|---|
| 687 |
|
|---|
| 688 | if (responseType === 'text') {
|
|---|
| 689 | convertedData = convertedData.toString(responseEncoding);
|
|---|
| 690 |
|
|---|
| 691 | if (!responseEncoding || responseEncoding === 'utf8') {
|
|---|
| 692 | convertedData = utils.stripBOM(convertedData);
|
|---|
| 693 | }
|
|---|
| 694 | } else if (responseType === 'stream') {
|
|---|
| 695 | convertedData = stream.Readable.from(convertedData);
|
|---|
| 696 | }
|
|---|
| 697 |
|
|---|
| 698 | return settle(resolve, reject, {
|
|---|
| 699 | data: convertedData,
|
|---|
| 700 | status: 200,
|
|---|
| 701 | statusText: 'OK',
|
|---|
| 702 | headers: new AxiosHeaders(),
|
|---|
| 703 | config,
|
|---|
| 704 | });
|
|---|
| 705 | }
|
|---|
| 706 |
|
|---|
| 707 | if (supportedProtocols.indexOf(protocol) === -1) {
|
|---|
| 708 | return reject(
|
|---|
| 709 | new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_BAD_REQUEST, config)
|
|---|
| 710 | );
|
|---|
| 711 | }
|
|---|
| 712 |
|
|---|
| 713 | const headers = AxiosHeaders.from(config.headers).normalize();
|
|---|
| 714 |
|
|---|
| 715 | // Set User-Agent (required by some servers)
|
|---|
| 716 | // See https://github.com/axios/axios/issues/69
|
|---|
| 717 | // User-Agent is specified; handle case where no UA header is desired
|
|---|
| 718 | // Only set header if it hasn't been set in config
|
|---|
| 719 | headers.set('User-Agent', 'axios/' + VERSION, false);
|
|---|
| 720 |
|
|---|
| 721 | const { onUploadProgress, onDownloadProgress } = config;
|
|---|
| 722 | const maxRate = config.maxRate;
|
|---|
| 723 | let maxUploadRate = undefined;
|
|---|
| 724 | let maxDownloadRate = undefined;
|
|---|
| 725 |
|
|---|
| 726 | // support for spec compliant FormData objects
|
|---|
| 727 | if (utils.isSpecCompliantForm(data)) {
|
|---|
| 728 | const userBoundary = headers.getContentType(/boundary=([-_\w\d]{10,70})/i);
|
|---|
| 729 |
|
|---|
| 730 | data = formDataToStream(
|
|---|
| 731 | data,
|
|---|
| 732 | (formHeaders) => {
|
|---|
| 733 | headers.set(formHeaders);
|
|---|
| 734 | },
|
|---|
| 735 | {
|
|---|
| 736 | tag: `axios-${VERSION}-boundary`,
|
|---|
| 737 | boundary: (userBoundary && userBoundary[1]) || undefined,
|
|---|
| 738 | }
|
|---|
| 739 | );
|
|---|
| 740 | // support for https://www.npmjs.com/package/form-data api
|
|---|
| 741 | } else if (
|
|---|
| 742 | utils.isFormData(data) &&
|
|---|
| 743 | utils.isFunction(data.getHeaders) &&
|
|---|
| 744 | data.getHeaders !== Object.prototype.getHeaders
|
|---|
| 745 | ) {
|
|---|
| 746 | setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
|
|---|
| 747 |
|
|---|
| 748 | if (!headers.hasContentLength()) {
|
|---|
| 749 | try {
|
|---|
| 750 | const knownLength = await util.promisify(data.getLength).call(data);
|
|---|
| 751 | Number.isFinite(knownLength) &&
|
|---|
| 752 | knownLength >= 0 &&
|
|---|
| 753 | headers.setContentLength(knownLength);
|
|---|
| 754 | /*eslint no-empty:0*/
|
|---|
| 755 | } catch (e) {}
|
|---|
| 756 | }
|
|---|
| 757 | } else if (utils.isBlob(data) || utils.isFile(data)) {
|
|---|
| 758 | data.size && headers.setContentType(data.type || 'application/octet-stream');
|
|---|
| 759 | headers.setContentLength(data.size || 0);
|
|---|
| 760 | data = stream.Readable.from(readBlob(data));
|
|---|
| 761 | } else if (data && !utils.isStream(data)) {
|
|---|
| 762 | if (Buffer.isBuffer(data)) {
|
|---|
| 763 | // Nothing to do...
|
|---|
| 764 | } else if (utils.isArrayBuffer(data)) {
|
|---|
| 765 | data = Buffer.from(new Uint8Array(data));
|
|---|
| 766 | } else if (utils.isString(data)) {
|
|---|
| 767 | data = Buffer.from(data, 'utf-8');
|
|---|
| 768 | } else {
|
|---|
| 769 | return reject(
|
|---|
| 770 | new AxiosError(
|
|---|
| 771 | 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
|
|---|
| 772 | AxiosError.ERR_BAD_REQUEST,
|
|---|
| 773 | config
|
|---|
| 774 | )
|
|---|
| 775 | );
|
|---|
| 776 | }
|
|---|
| 777 |
|
|---|
| 778 | // Add Content-Length header if data exists
|
|---|
| 779 | headers.setContentLength(data.length, false);
|
|---|
| 780 |
|
|---|
| 781 | if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
|
|---|
| 782 | return reject(
|
|---|
| 783 | new AxiosError(
|
|---|
| 784 | 'Request body larger than maxBodyLength limit',
|
|---|
| 785 | AxiosError.ERR_BAD_REQUEST,
|
|---|
| 786 | config
|
|---|
| 787 | )
|
|---|
| 788 | );
|
|---|
| 789 | }
|
|---|
| 790 | }
|
|---|
| 791 |
|
|---|
| 792 | const contentLength = utils.toFiniteNumber(headers.getContentLength());
|
|---|
| 793 |
|
|---|
| 794 | if (utils.isArray(maxRate)) {
|
|---|
| 795 | maxUploadRate = maxRate[0];
|
|---|
| 796 | maxDownloadRate = maxRate[1];
|
|---|
| 797 | } else {
|
|---|
| 798 | maxUploadRate = maxDownloadRate = maxRate;
|
|---|
| 799 | }
|
|---|
| 800 |
|
|---|
| 801 | if (data && (onUploadProgress || maxUploadRate)) {
|
|---|
| 802 | if (!utils.isStream(data)) {
|
|---|
| 803 | data = stream.Readable.from(data, { objectMode: false });
|
|---|
| 804 | }
|
|---|
| 805 |
|
|---|
| 806 | data = stream.pipeline(
|
|---|
| 807 | [
|
|---|
| 808 | data,
|
|---|
| 809 | new AxiosTransformStream({
|
|---|
| 810 | maxRate: utils.toFiniteNumber(maxUploadRate),
|
|---|
| 811 | }),
|
|---|
| 812 | ],
|
|---|
| 813 | utils.noop
|
|---|
| 814 | );
|
|---|
| 815 |
|
|---|
| 816 | onUploadProgress &&
|
|---|
| 817 | data.on(
|
|---|
| 818 | 'progress',
|
|---|
| 819 | flushOnFinish(
|
|---|
| 820 | data,
|
|---|
| 821 | progressEventDecorator(
|
|---|
| 822 | contentLength,
|
|---|
| 823 | progressEventReducer(asyncDecorator(onUploadProgress), false, 3)
|
|---|
| 824 | )
|
|---|
| 825 | )
|
|---|
| 826 | );
|
|---|
| 827 | }
|
|---|
| 828 |
|
|---|
| 829 | // HTTP basic authentication
|
|---|
| 830 | let auth = undefined;
|
|---|
| 831 | const configAuth = own('auth');
|
|---|
| 832 | if (configAuth) {
|
|---|
| 833 | const username = configAuth.username || '';
|
|---|
| 834 | const password = configAuth.password || '';
|
|---|
| 835 | auth = username + ':' + password;
|
|---|
| 836 | }
|
|---|
| 837 |
|
|---|
| 838 | if (!auth && parsed.username) {
|
|---|
| 839 | const urlUsername = decodeURIComponentSafe(parsed.username);
|
|---|
| 840 | const urlPassword = decodeURIComponentSafe(parsed.password);
|
|---|
| 841 | auth = urlUsername + ':' + urlPassword;
|
|---|
| 842 | }
|
|---|
| 843 |
|
|---|
| 844 | auth && headers.delete('authorization');
|
|---|
| 845 |
|
|---|
| 846 | let path;
|
|---|
| 847 |
|
|---|
| 848 | try {
|
|---|
| 849 | path = buildURL(
|
|---|
| 850 | parsed.pathname + parsed.search,
|
|---|
| 851 | config.params,
|
|---|
| 852 | config.paramsSerializer
|
|---|
| 853 | ).replace(/^\?/, '');
|
|---|
| 854 | } catch (err) {
|
|---|
| 855 | const customErr = new Error(err.message);
|
|---|
| 856 | customErr.config = config;
|
|---|
| 857 | customErr.url = config.url;
|
|---|
| 858 | customErr.exists = true;
|
|---|
| 859 | return reject(customErr);
|
|---|
| 860 | }
|
|---|
| 861 |
|
|---|
| 862 | headers.set(
|
|---|
| 863 | 'Accept-Encoding',
|
|---|
| 864 | 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''),
|
|---|
| 865 | false
|
|---|
| 866 | );
|
|---|
| 867 |
|
|---|
| 868 | // Null-prototype to block prototype pollution gadgets on properties read
|
|---|
| 869 | // directly by Node's http.request (e.g. insecureHTTPParser, lookup).
|
|---|
| 870 | const options = Object.assign(Object.create(null), {
|
|---|
| 871 | path,
|
|---|
| 872 | method: method,
|
|---|
| 873 | headers: toByteStringHeaderObject(headers),
|
|---|
| 874 | agents: { http: config.httpAgent, https: config.httpsAgent },
|
|---|
| 875 | auth,
|
|---|
| 876 | protocol,
|
|---|
| 877 | family,
|
|---|
| 878 | beforeRedirect: dispatchBeforeRedirect,
|
|---|
| 879 | beforeRedirects: Object.create(null),
|
|---|
| 880 | http2Options,
|
|---|
| 881 | });
|
|---|
| 882 |
|
|---|
| 883 | // cacheable-lookup integration hotfix
|
|---|
| 884 | !utils.isUndefined(lookup) && (options.lookup = lookup);
|
|---|
| 885 |
|
|---|
| 886 | if (config.socketPath) {
|
|---|
| 887 | if (typeof config.socketPath !== 'string') {
|
|---|
| 888 | return reject(
|
|---|
| 889 | new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)
|
|---|
| 890 | );
|
|---|
| 891 | }
|
|---|
| 892 |
|
|---|
| 893 | if (config.allowedSocketPaths != null) {
|
|---|
| 894 | const allowed = Array.isArray(config.allowedSocketPaths)
|
|---|
| 895 | ? config.allowedSocketPaths
|
|---|
| 896 | : [config.allowedSocketPaths];
|
|---|
| 897 |
|
|---|
| 898 | const resolvedSocket = resolvePath(config.socketPath);
|
|---|
| 899 | const isAllowed = allowed.some(
|
|---|
| 900 | (entry) => typeof entry === 'string' && resolvePath(entry) === resolvedSocket
|
|---|
| 901 | );
|
|---|
| 902 |
|
|---|
| 903 | if (!isAllowed) {
|
|---|
| 904 | return reject(
|
|---|
| 905 | new AxiosError(
|
|---|
| 906 | `socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`,
|
|---|
| 907 | AxiosError.ERR_BAD_OPTION_VALUE,
|
|---|
| 908 | config
|
|---|
| 909 | )
|
|---|
| 910 | );
|
|---|
| 911 | }
|
|---|
| 912 | }
|
|---|
| 913 |
|
|---|
| 914 | options.socketPath = config.socketPath;
|
|---|
| 915 | } else {
|
|---|
| 916 | options.hostname = parsed.hostname.startsWith('[')
|
|---|
| 917 | ? parsed.hostname.slice(1, -1)
|
|---|
| 918 | : parsed.hostname;
|
|---|
| 919 | options.port = parsed.port;
|
|---|
| 920 | setProxy(
|
|---|
| 921 | options,
|
|---|
| 922 | config.proxy,
|
|---|
| 923 | protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path,
|
|---|
| 924 | false,
|
|---|
| 925 | config.httpsAgent
|
|---|
| 926 | );
|
|---|
| 927 | }
|
|---|
| 928 | let transport;
|
|---|
| 929 | let isNativeTransport = false;
|
|---|
| 930 | const isHttpsRequest = isHttps.test(options.protocol);
|
|---|
| 931 | // Don't clobber a CONNECT-tunneling agent installed by setProxy() for an
|
|---|
| 932 | // HTTPS target.
|
|---|
| 933 | if (options.agent == null) {
|
|---|
| 934 | options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
|
|---|
| 935 | }
|
|---|
| 936 |
|
|---|
| 937 | if (isHttp2) {
|
|---|
| 938 | transport = http2Transport;
|
|---|
| 939 | } else {
|
|---|
| 940 | const configTransport = own('transport');
|
|---|
| 941 | if (configTransport) {
|
|---|
| 942 | transport = configTransport;
|
|---|
| 943 | } else if (config.maxRedirects === 0) {
|
|---|
| 944 | transport = isHttpsRequest ? https : http;
|
|---|
| 945 | isNativeTransport = true;
|
|---|
| 946 | } else {
|
|---|
| 947 | if (config.maxRedirects) {
|
|---|
| 948 | options.maxRedirects = config.maxRedirects;
|
|---|
| 949 | }
|
|---|
| 950 | const configBeforeRedirect = own('beforeRedirect');
|
|---|
| 951 | if (configBeforeRedirect) {
|
|---|
| 952 | options.beforeRedirects.config = configBeforeRedirect;
|
|---|
| 953 | }
|
|---|
| 954 | transport = isHttpsRequest ? httpsFollow : httpFollow;
|
|---|
| 955 | }
|
|---|
| 956 | }
|
|---|
| 957 |
|
|---|
| 958 | if (config.maxBodyLength > -1) {
|
|---|
| 959 | options.maxBodyLength = config.maxBodyLength;
|
|---|
| 960 | } else {
|
|---|
| 961 | // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
|
|---|
| 962 | options.maxBodyLength = Infinity;
|
|---|
| 963 | }
|
|---|
| 964 |
|
|---|
| 965 | // Always set an explicit own value so a polluted
|
|---|
| 966 | // Object.prototype.insecureHTTPParser cannot enable the lenient parser
|
|---|
| 967 | // through Node's internal options copy
|
|---|
| 968 | options.insecureHTTPParser = Boolean(own('insecureHTTPParser'));
|
|---|
| 969 |
|
|---|
| 970 | // Create the request
|
|---|
| 971 | req = transport.request(options, function handleResponse(res) {
|
|---|
| 972 | clearConnectPhaseTimer();
|
|---|
| 973 |
|
|---|
| 974 | if (req.destroyed) return;
|
|---|
| 975 |
|
|---|
| 976 | const streams = [res];
|
|---|
| 977 |
|
|---|
| 978 | const responseLength = utils.toFiniteNumber(res.headers['content-length']);
|
|---|
| 979 |
|
|---|
| 980 | if (onDownloadProgress || maxDownloadRate) {
|
|---|
| 981 | const transformStream = new AxiosTransformStream({
|
|---|
| 982 | maxRate: utils.toFiniteNumber(maxDownloadRate),
|
|---|
| 983 | });
|
|---|
| 984 |
|
|---|
| 985 | onDownloadProgress &&
|
|---|
| 986 | transformStream.on(
|
|---|
| 987 | 'progress',
|
|---|
| 988 | flushOnFinish(
|
|---|
| 989 | transformStream,
|
|---|
| 990 | progressEventDecorator(
|
|---|
| 991 | responseLength,
|
|---|
| 992 | progressEventReducer(asyncDecorator(onDownloadProgress), true, 3)
|
|---|
| 993 | )
|
|---|
| 994 | )
|
|---|
| 995 | );
|
|---|
| 996 |
|
|---|
| 997 | streams.push(transformStream);
|
|---|
| 998 | }
|
|---|
| 999 |
|
|---|
| 1000 | // decompress the response body transparently if required
|
|---|
| 1001 | let responseStream = res;
|
|---|
| 1002 |
|
|---|
| 1003 | // return the last request in case of redirects
|
|---|
| 1004 | const lastRequest = res.req || req;
|
|---|
| 1005 |
|
|---|
| 1006 | // if decompress disabled we should not decompress
|
|---|
| 1007 | if (config.decompress !== false && res.headers['content-encoding']) {
|
|---|
| 1008 | // if no content, but headers still say that it is encoded,
|
|---|
| 1009 | // remove the header not confuse downstream operations
|
|---|
| 1010 | if (method === 'HEAD' || res.statusCode === 204) {
|
|---|
| 1011 | delete res.headers['content-encoding'];
|
|---|
| 1012 | }
|
|---|
| 1013 |
|
|---|
| 1014 | switch ((res.headers['content-encoding'] || '').toLowerCase()) {
|
|---|
| 1015 | /*eslint default-case:0*/
|
|---|
| 1016 | case 'gzip':
|
|---|
| 1017 | case 'x-gzip':
|
|---|
| 1018 | case 'compress':
|
|---|
| 1019 | case 'x-compress':
|
|---|
| 1020 | // add the unzipper to the body stream processing pipeline
|
|---|
| 1021 | streams.push(zlib.createUnzip(zlibOptions));
|
|---|
| 1022 |
|
|---|
| 1023 | // remove the content-encoding in order to not confuse downstream operations
|
|---|
| 1024 | delete res.headers['content-encoding'];
|
|---|
| 1025 | break;
|
|---|
| 1026 | case 'deflate':
|
|---|
| 1027 | streams.push(new ZlibHeaderTransformStream());
|
|---|
| 1028 |
|
|---|
| 1029 | // add the unzipper to the body stream processing pipeline
|
|---|
| 1030 | streams.push(zlib.createUnzip(zlibOptions));
|
|---|
| 1031 |
|
|---|
| 1032 | // remove the content-encoding in order to not confuse downstream operations
|
|---|
| 1033 | delete res.headers['content-encoding'];
|
|---|
| 1034 | break;
|
|---|
| 1035 | case 'br':
|
|---|
| 1036 | if (isBrotliSupported) {
|
|---|
| 1037 | streams.push(zlib.createBrotliDecompress(brotliOptions));
|
|---|
| 1038 | delete res.headers['content-encoding'];
|
|---|
| 1039 | }
|
|---|
| 1040 | }
|
|---|
| 1041 | }
|
|---|
| 1042 |
|
|---|
| 1043 | responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
|
|---|
| 1044 |
|
|---|
| 1045 | const response = {
|
|---|
| 1046 | status: res.statusCode,
|
|---|
| 1047 | statusText: res.statusMessage,
|
|---|
| 1048 | headers: new AxiosHeaders(res.headers),
|
|---|
| 1049 | config,
|
|---|
| 1050 | request: lastRequest,
|
|---|
| 1051 | };
|
|---|
| 1052 |
|
|---|
| 1053 | if (responseType === 'stream') {
|
|---|
| 1054 | // Enforce maxContentLength on streamed responses; previously this
|
|---|
| 1055 | // was applied only to buffered responses.
|
|---|
| 1056 | if (config.maxContentLength > -1) {
|
|---|
| 1057 | const limit = config.maxContentLength;
|
|---|
| 1058 | const source = responseStream;
|
|---|
| 1059 | async function* enforceMaxContentLength() {
|
|---|
| 1060 | let totalResponseBytes = 0;
|
|---|
| 1061 | for await (const chunk of source) {
|
|---|
| 1062 | totalResponseBytes += chunk.length;
|
|---|
| 1063 | if (totalResponseBytes > limit) {
|
|---|
| 1064 | throw new AxiosError(
|
|---|
| 1065 | 'maxContentLength size of ' + limit + ' exceeded',
|
|---|
| 1066 | AxiosError.ERR_BAD_RESPONSE,
|
|---|
| 1067 | config,
|
|---|
| 1068 | lastRequest
|
|---|
| 1069 | );
|
|---|
| 1070 | }
|
|---|
| 1071 | yield chunk;
|
|---|
| 1072 | }
|
|---|
| 1073 | }
|
|---|
| 1074 | responseStream = stream.Readable.from(enforceMaxContentLength(), {
|
|---|
| 1075 | objectMode: false,
|
|---|
| 1076 | });
|
|---|
| 1077 | }
|
|---|
| 1078 | response.data = responseStream;
|
|---|
| 1079 | settle(resolve, reject, response);
|
|---|
| 1080 | } else {
|
|---|
| 1081 | const responseBuffer = [];
|
|---|
| 1082 | let totalResponseBytes = 0;
|
|---|
| 1083 |
|
|---|
| 1084 | responseStream.on('data', function handleStreamData(chunk) {
|
|---|
| 1085 | responseBuffer.push(chunk);
|
|---|
| 1086 | totalResponseBytes += chunk.length;
|
|---|
| 1087 |
|
|---|
| 1088 | // make sure the content length is not over the maxContentLength if specified
|
|---|
| 1089 | if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
|
|---|
| 1090 | // stream.destroy() emit aborted event before calling reject() on Node.js v16
|
|---|
| 1091 | rejected = true;
|
|---|
| 1092 | responseStream.destroy();
|
|---|
| 1093 | abort(
|
|---|
| 1094 | new AxiosError(
|
|---|
| 1095 | 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
|
|---|
| 1096 | AxiosError.ERR_BAD_RESPONSE,
|
|---|
| 1097 | config,
|
|---|
| 1098 | lastRequest
|
|---|
| 1099 | )
|
|---|
| 1100 | );
|
|---|
| 1101 | }
|
|---|
| 1102 | });
|
|---|
| 1103 |
|
|---|
| 1104 | responseStream.on('aborted', function handlerStreamAborted() {
|
|---|
| 1105 | if (rejected) {
|
|---|
| 1106 | return;
|
|---|
| 1107 | }
|
|---|
| 1108 |
|
|---|
| 1109 | const err = new AxiosError(
|
|---|
| 1110 | 'stream has been aborted',
|
|---|
| 1111 | AxiosError.ERR_BAD_RESPONSE,
|
|---|
| 1112 | config,
|
|---|
| 1113 | lastRequest,
|
|---|
| 1114 | response
|
|---|
| 1115 | );
|
|---|
| 1116 | responseStream.destroy(err);
|
|---|
| 1117 | reject(err);
|
|---|
| 1118 | });
|
|---|
| 1119 |
|
|---|
| 1120 | responseStream.on('error', function handleStreamError(err) {
|
|---|
| 1121 | if (rejected) return;
|
|---|
| 1122 | reject(AxiosError.from(err, null, config, lastRequest, response));
|
|---|
| 1123 | });
|
|---|
| 1124 |
|
|---|
| 1125 | responseStream.on('end', function handleStreamEnd() {
|
|---|
| 1126 | try {
|
|---|
| 1127 | let responseData =
|
|---|
| 1128 | responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
|
|---|
| 1129 | if (responseType !== 'arraybuffer') {
|
|---|
| 1130 | responseData = responseData.toString(responseEncoding);
|
|---|
| 1131 | if (!responseEncoding || responseEncoding === 'utf8') {
|
|---|
| 1132 | responseData = utils.stripBOM(responseData);
|
|---|
| 1133 | }
|
|---|
| 1134 | }
|
|---|
| 1135 | response.data = responseData;
|
|---|
| 1136 | } catch (err) {
|
|---|
| 1137 | return reject(AxiosError.from(err, null, config, response.request, response));
|
|---|
| 1138 | }
|
|---|
| 1139 | settle(resolve, reject, response);
|
|---|
| 1140 | });
|
|---|
| 1141 | }
|
|---|
| 1142 |
|
|---|
| 1143 | abortEmitter.once('abort', (err) => {
|
|---|
| 1144 | if (!responseStream.destroyed) {
|
|---|
| 1145 | responseStream.emit('error', err);
|
|---|
| 1146 | responseStream.destroy();
|
|---|
| 1147 | }
|
|---|
| 1148 | });
|
|---|
| 1149 | });
|
|---|
| 1150 |
|
|---|
| 1151 | abortEmitter.once('abort', (err) => {
|
|---|
| 1152 | if (req.close) {
|
|---|
| 1153 | req.close();
|
|---|
| 1154 | } else {
|
|---|
| 1155 | req.destroy(err);
|
|---|
| 1156 | }
|
|---|
| 1157 | });
|
|---|
| 1158 |
|
|---|
| 1159 | // Handle errors
|
|---|
| 1160 | req.on('error', function handleRequestError(err) {
|
|---|
| 1161 | reject(AxiosError.from(err, null, config, req));
|
|---|
| 1162 | });
|
|---|
| 1163 |
|
|---|
| 1164 | // set tcp keep alive to prevent drop connection by peer
|
|---|
| 1165 | // Track every socket bound to this outer RedirectableRequest so a single
|
|---|
| 1166 | // 'close' listener can release ownership on all of them. follow-redirects
|
|---|
| 1167 | // re-emits the 'socket' event for each hop's native request onto the same
|
|---|
| 1168 | // outer request, so attaching per-request listeners inside this handler
|
|---|
| 1169 | // would accumulate across hops and trigger MaxListenersExceededWarning at
|
|---|
| 1170 | // >= 11 redirects. Clearing only the last-bound socket would leave stale
|
|---|
| 1171 | // kAxiosCurrentReq refs on earlier hop sockets returned to the keep-alive
|
|---|
| 1172 | // pool, causing an idle-pool 'error' to be attributed to a closed req.
|
|---|
| 1173 | const boundSockets = new Set();
|
|---|
| 1174 |
|
|---|
| 1175 | req.on('socket', function handleRequestSocket(socket) {
|
|---|
| 1176 | // default interval of sending ack packet is 1 minute
|
|---|
| 1177 | socket.setKeepAlive(true, 1000 * 60);
|
|---|
| 1178 |
|
|---|
| 1179 | // Install a single 'error' listener per socket (not per request) to avoid
|
|---|
| 1180 | // accumulating listeners on pooled keep-alive sockets that get reassigned
|
|---|
| 1181 | // to new requests before the previous request's 'close' fires (issue #10780).
|
|---|
| 1182 | // The listener is bound to the socket's currently-active request via a
|
|---|
| 1183 | // symbol, which is swapped as the socket is reassigned.
|
|---|
| 1184 | if (!socket[kAxiosSocketListener]) {
|
|---|
| 1185 | socket.on('error', function handleSocketError(err) {
|
|---|
| 1186 | const current = socket[kAxiosCurrentReq];
|
|---|
| 1187 | if (current && !current.destroyed) {
|
|---|
| 1188 | current.destroy(err);
|
|---|
| 1189 | }
|
|---|
| 1190 | });
|
|---|
| 1191 | socket[kAxiosSocketListener] = true;
|
|---|
| 1192 | }
|
|---|
| 1193 |
|
|---|
| 1194 | socket[kAxiosCurrentReq] = req;
|
|---|
| 1195 | boundSockets.add(socket);
|
|---|
| 1196 | });
|
|---|
| 1197 |
|
|---|
| 1198 | req.once('close', function clearCurrentReq() {
|
|---|
| 1199 | clearConnectPhaseTimer();
|
|---|
| 1200 |
|
|---|
| 1201 | for (const socket of boundSockets) {
|
|---|
| 1202 | if (socket[kAxiosCurrentReq] === req) {
|
|---|
| 1203 | socket[kAxiosCurrentReq] = null;
|
|---|
| 1204 | }
|
|---|
| 1205 | }
|
|---|
| 1206 | boundSockets.clear();
|
|---|
| 1207 | });
|
|---|
| 1208 |
|
|---|
| 1209 | // Handle request timeout
|
|---|
| 1210 | if (config.timeout) {
|
|---|
| 1211 | // This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
|
|---|
| 1212 | const timeout = parseInt(config.timeout, 10);
|
|---|
| 1213 |
|
|---|
| 1214 | if (Number.isNaN(timeout)) {
|
|---|
| 1215 | abort(
|
|---|
| 1216 | new AxiosError(
|
|---|
| 1217 | 'error trying to parse `config.timeout` to int',
|
|---|
| 1218 | AxiosError.ERR_BAD_OPTION_VALUE,
|
|---|
| 1219 | config,
|
|---|
| 1220 | req
|
|---|
| 1221 | )
|
|---|
| 1222 | );
|
|---|
| 1223 |
|
|---|
| 1224 | return;
|
|---|
| 1225 | }
|
|---|
| 1226 |
|
|---|
| 1227 | const handleTimeout = function handleTimeout() {
|
|---|
| 1228 | if (isDone) return;
|
|---|
| 1229 | abort(createTimeoutError());
|
|---|
| 1230 | };
|
|---|
| 1231 |
|
|---|
| 1232 | if (isNativeTransport && timeout > 0) {
|
|---|
| 1233 | // Native ClientRequest#setTimeout starts from the socket lifecycle and
|
|---|
| 1234 | // may not fire while TCP connect is still pending. Mirror the
|
|---|
| 1235 | // follow-redirects wall-clock timer for the maxRedirects === 0 path.
|
|---|
| 1236 | connectPhaseTimer = setTimeout(handleTimeout, timeout);
|
|---|
| 1237 | }
|
|---|
| 1238 |
|
|---|
| 1239 | // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
|
|---|
| 1240 | // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
|
|---|
| 1241 | // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
|
|---|
| 1242 | // And then these socket which be hang up will devouring CPU little by little.
|
|---|
| 1243 | // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
|
|---|
| 1244 | req.setTimeout(timeout, handleTimeout);
|
|---|
| 1245 | } else {
|
|---|
| 1246 | // explicitly reset the socket timeout value for a possible `keep-alive` request
|
|---|
| 1247 | req.setTimeout(0);
|
|---|
| 1248 | }
|
|---|
| 1249 |
|
|---|
| 1250 | // Send the request
|
|---|
| 1251 | if (utils.isStream(data)) {
|
|---|
| 1252 | let ended = false;
|
|---|
| 1253 | let errored = false;
|
|---|
| 1254 |
|
|---|
| 1255 | data.on('end', () => {
|
|---|
| 1256 | ended = true;
|
|---|
| 1257 | });
|
|---|
| 1258 |
|
|---|
| 1259 | data.once('error', (err) => {
|
|---|
| 1260 | errored = true;
|
|---|
| 1261 | req.destroy(err);
|
|---|
| 1262 | });
|
|---|
| 1263 |
|
|---|
| 1264 | data.on('close', () => {
|
|---|
| 1265 | if (!ended && !errored) {
|
|---|
| 1266 | abort(new CanceledError('Request stream has been aborted', config, req));
|
|---|
| 1267 | }
|
|---|
| 1268 | });
|
|---|
| 1269 |
|
|---|
| 1270 | // Enforce maxBodyLength for streamed uploads on the native http/https
|
|---|
| 1271 | // transport (maxRedirects === 0); follow-redirects enforces it on the
|
|---|
| 1272 | // other path.
|
|---|
| 1273 | let uploadStream = data;
|
|---|
| 1274 | if (config.maxBodyLength > -1 && config.maxRedirects === 0) {
|
|---|
| 1275 | const limit = config.maxBodyLength;
|
|---|
| 1276 | let bytesSent = 0;
|
|---|
| 1277 | uploadStream = stream.pipeline(
|
|---|
| 1278 | [
|
|---|
| 1279 | data,
|
|---|
| 1280 | new stream.Transform({
|
|---|
| 1281 | transform(chunk, _enc, cb) {
|
|---|
| 1282 | bytesSent += chunk.length;
|
|---|
| 1283 | if (bytesSent > limit) {
|
|---|
| 1284 | return cb(
|
|---|
| 1285 | new AxiosError(
|
|---|
| 1286 | 'Request body larger than maxBodyLength limit',
|
|---|
| 1287 | AxiosError.ERR_BAD_REQUEST,
|
|---|
| 1288 | config,
|
|---|
| 1289 | req
|
|---|
| 1290 | )
|
|---|
| 1291 | );
|
|---|
| 1292 | }
|
|---|
| 1293 | cb(null, chunk);
|
|---|
| 1294 | },
|
|---|
| 1295 | }),
|
|---|
| 1296 | ],
|
|---|
| 1297 | utils.noop
|
|---|
| 1298 | );
|
|---|
| 1299 | uploadStream.on('error', (err) => {
|
|---|
| 1300 | if (!req.destroyed) req.destroy(err);
|
|---|
| 1301 | });
|
|---|
| 1302 | }
|
|---|
| 1303 |
|
|---|
| 1304 | uploadStream.pipe(req);
|
|---|
| 1305 | } else {
|
|---|
| 1306 | data && req.write(data);
|
|---|
| 1307 | req.end();
|
|---|
| 1308 | }
|
|---|
| 1309 | });
|
|---|
| 1310 | };
|
|---|
| 1311 |
|
|---|
| 1312 | export const __setProxy = setProxy;
|
|---|