source: frontend/node_modules/axios/lib/adapters/http.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 13 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 43.9 KB
Line 
1import utils from '../utils.js';
2import settle from '../core/settle.js';
3import buildFullPath from '../core/buildFullPath.js';
4import buildURL from '../helpers/buildURL.js';
5import { getProxyForUrl } from 'proxy-from-env';
6import HttpsProxyAgent from 'https-proxy-agent';
7import http from 'http';
8import https from 'https';
9import http2 from 'http2';
10import util from 'util';
11import { resolve as resolvePath } from 'path';
12import followRedirects from 'follow-redirects';
13import zlib from 'zlib';
14import { VERSION } from '../env/data.js';
15import transitionalDefaults from '../defaults/transitional.js';
16import AxiosError from '../core/AxiosError.js';
17import CanceledError from '../cancel/CanceledError.js';
18import platform from '../platform/index.js';
19import fromDataURI from '../helpers/fromDataURI.js';
20import stream from 'stream';
21import AxiosHeaders from '../core/AxiosHeaders.js';
22import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
23import { EventEmitter } from 'events';
24import formDataToStream from '../helpers/formDataToStream.js';
25import readBlob from '../helpers/readBlob.js';
26import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
27import callbackify from '../helpers/callbackify.js';
28import shouldBypassProxy from '../helpers/shouldBypassProxy.js';
29import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
30import {
31 progressEventReducer,
32 progressEventDecorator,
33 asyncDecorator,
34} from '../helpers/progressEventReducer.js';
35import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
36
37const zlibOptions = {
38 flush: zlib.constants.Z_SYNC_FLUSH,
39 finishFlush: zlib.constants.Z_SYNC_FLUSH,
40};
41
42const brotliOptions = {
43 flush: zlib.constants.BROTLI_OPERATION_FLUSH,
44 finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
45};
46
47const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
48
49const { http: httpFollow, https: httpsFollow } = followRedirects;
50
51const isHttps = /https:?/;
52const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
53
54function 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).
69const kAxiosSocketListener = Symbol('axios.http.socketListener');
70const 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.
75const 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.
81const tunnelingAgentCache = new Map();
82const tunnelingAgentCacheUser = new WeakMap();
83
84function 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
111const 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.
119const 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
131const flushOnFinish = (stream, [throttled, flush]) => {
132 stream.on('end', flush).on('error', flush);
133
134 return throttled;
135};
136
137class 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
240const 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 */
250function 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 */
268function 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
425const isHttpAdapterSupported =
426 typeof process !== 'undefined' && utils.kindOf(process) === 'process';
427
428// temporary hotfix
429
430const 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
455const 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
465const buildAddressEntry = (address, family) =>
466 resolveFamily(utils.isObject(address) ? address : { address, family });
467
468const 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*/
517export 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
1312export const __setProxy = setProxy;
Note: See TracBrowser for help on using the repository browser.