@vscode/proxy-agent
Version:
NodeJS http(s) agent implementation for VS Code
1,066 lines • 61.2 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Nathan Rajlich, Félicien François, Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.toLogString = exports.resetCaches = exports.loadSystemCertificates = exports.getOrLoadAdditionalCertificates = exports.patchUndici = exports.setProxyAuthorizationHeader = exports.createWebSocketPatch = exports.createFetchPatch = exports.createTlsPatch = exports.createNetPatch = exports.createHttpPatch = exports.createProxyResolver = exports.createProxyAuthorizationLookup = exports.LogLevel = void 0;
const net = __importStar(require("net"));
const tls = __importStar(require("tls"));
const nodeurl = __importStar(require("url"));
const os = __importStar(require("os"));
const fs = __importStar(require("fs"));
const cp = __importStar(require("child_process"));
const crypto = __importStar(require("crypto"));
const undici = __importStar(require("undici"));
const agent_1 = require("./agent");
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Trace"] = 0] = "Trace";
LogLevel[LogLevel["Debug"] = 1] = "Debug";
LogLevel[LogLevel["Info"] = 2] = "Info";
LogLevel[LogLevel["Warning"] = 3] = "Warning";
LogLevel[LogLevel["Error"] = 4] = "Error";
LogLevel[LogLevel["Critical"] = 5] = "Critical";
LogLevel[LogLevel["Off"] = 6] = "Off";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
function createProxyAuthorizationLookup(params) {
const proxyAuthenticateCache = {};
const basicAuthCache = {};
return (proxyURL, proxyAuthenticate, state) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
proxyURL = proxyURL.replace(/\/+$/, '');
const cached = proxyAuthenticateCache[proxyURL];
if (proxyAuthenticate) {
proxyAuthenticateCache[proxyURL] = proxyAuthenticate;
}
params.log.trace('ProxyResolver#lookupProxyAuthorization callback', `proxyURL:${proxyURL}`, `proxyAuthenticate:${proxyAuthenticate}`, `proxyAuthenticateCache:${cached}`);
const header = proxyAuthenticate || cached;
const authenticate = Array.isArray(header) ? header : typeof header === 'string' ? [header] : [];
(_a = params.onDidRequestAuthentication) === null || _a === void 0 ? void 0 : _a.call(params, authenticate);
if (params.lookupKerberosAuthorization && authenticate.some(value => /^(Negotiate|Kerberos)( |$)/i.test(value)) && !state.kerberosRequested) {
state.kerberosRequested = true;
try {
const authorization = yield params.lookupKerberosAuthorization(proxyURL);
if (authorization) {
return authorization;
}
}
catch (error) {
params.log.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', error);
}
}
const basicAuthHeader = authenticate.find(value => /^Basic( |$)/i.test(value));
if (params.lookupAuthorization && basicAuthHeader) {
try {
const cachedAuth = basicAuthCache[proxyURL];
if (cachedAuth) {
if (state.basicAuthCacheUsed) {
params.log.debug('ProxyResolver#lookupProxyAuthorization Basic authentication deleting cached credentials', `proxyURL:${proxyURL}`);
delete basicAuthCache[proxyURL];
}
else {
params.log.debug('ProxyResolver#lookupProxyAuthorization Basic authentication using cached credentials', `proxyURL:${proxyURL}`);
state.basicAuthCacheUsed = true;
return cachedAuth;
}
}
state.basicAuthAttempt = (state.basicAuthAttempt || 0) + 1;
const realm = (_b = / realm="([^"]+)"/i.exec(basicAuthHeader)) === null || _b === void 0 ? void 0 : _b[1];
params.log.debug('ProxyResolver#lookupProxyAuthorization Basic authentication lookup', `proxyURL:${proxyURL}`, `realm:${realm}`);
const url = new URL(proxyURL);
const credentials = yield params.lookupAuthorization({
scheme: 'basic',
host: url.hostname,
port: Number(url.port),
realm: realm || '',
isProxy: true,
attempt: state.basicAuthAttempt,
});
if (credentials) {
params.log.debug('ProxyResolver#lookupProxyAuthorization Basic authentication received credentials', `proxyURL:${proxyURL}`, `realm:${realm}`);
const authorization = 'Basic ' + Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
basicAuthCache[proxyURL] = authorization;
return authorization;
}
params.log.debug('ProxyResolver#lookupProxyAuthorization Basic authentication received no credentials', `proxyURL:${proxyURL}`, `realm:${realm}`);
}
catch (error) {
params.log.error('ProxyResolver#lookupProxyAuthorization Basic authentication failed', error);
}
}
return undefined;
});
}
exports.createProxyAuthorizationLookup = createProxyAuthorizationLookup;
function createProxyResolver(params) {
const { getProxyURL, log, proxyResolveTelemetry: proxyResolverTelemetry, env } = params;
let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized.
let envNoProxy = noProxyFromEnv(env.no_proxy || env.NO_PROXY); // Not standardized.
let cacheRolls = 0;
let oldCache = new Map();
let cache = new Map();
let lastNetworkInterfacesCheck = 0;
let lastNetworkInterfacesSnapshot;
function checkAndFlushCacheIfNetworkChanged() {
var _a, _b;
const intervalMs = (_b = (_a = params.getNetworkInterfaceCheckInterval) === null || _a === void 0 ? void 0 : _a.call(params)) !== null && _b !== void 0 ? _b : -1;
if (intervalMs < 0) {
return;
}
const now = Date.now();
if (now - lastNetworkInterfacesCheck >= intervalMs) {
lastNetworkInterfacesCheck = now;
const currentSnapshot = JSON.stringify(os.networkInterfaces());
if (lastNetworkInterfacesSnapshot) {
if (lastNetworkInterfacesSnapshot !== currentSnapshot) {
log.debug('ProxyResolver#getCachedProxy network interfaces changed, flushing cache');
cache.clear();
oldCache.clear();
}
else {
log.debug('ProxyResolver#getCachedProxy network interfaces unchanged');
}
}
lastNetworkInterfacesSnapshot = currentSnapshot;
}
}
function getCacheKey(url) {
// Expecting proxies to usually be the same per scheme://host:port. Assuming that for performance.
return nodeurl.format(Object.assign(Object.assign({}, url), { pathname: undefined, search: undefined, hash: undefined }));
}
function getCachedProxy(key) {
checkAndFlushCacheIfNetworkChanged();
let proxy = cache.get(key);
if (proxy) {
return proxy;
}
proxy = oldCache.get(key);
if (proxy) {
oldCache.delete(key);
cacheProxy(key, proxy);
}
return proxy;
}
function cacheProxy(key, proxy) {
cache.set(key, proxy);
if (cache.size >= maxCacheEntries) {
oldCache = cache;
cache = new Map();
cacheRolls++;
log.debug('ProxyResolver#cacheProxy cacheRolls', cacheRolls);
}
}
let timeout;
let count = 0;
let duration = 0;
let errorCount = 0;
let cacheCount = 0;
let envCount = 0;
let settingsCount = 0;
let localhostCount = 0;
let envNoProxyCount = 0;
let configNoProxyCount = 0;
let results = [];
function logEvent() {
timeout = undefined;
proxyResolverTelemetry({ count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, envNoProxyCount, configNoProxyCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = envNoProxyCount = configNoProxyCount = 0;
results = [];
}
function resolveProxyWithRequest(flags, req, opts, url, callback) {
if (!timeout) {
timeout = setTimeout(logEvent, 10 * 60 * 1000);
}
const stackText = ''; // getLogLevel() === LogLevel.Trace ? '\n' + new Error('Error for stack trace').stack : '';
addCertificatesToOptionsV1(params, flags.addCertificatesV1, opts, () => {
if (!flags.useProxySettings) {
callback('DIRECT');
return;
}
useProxySettings(url, req, stackText, callback);
}, flags.testCertificates);
}
function useProxySettings(url, req, stackText, callback) {
const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that.
const hostname = parsedUrl.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '::ffff:127.0.0.1') {
localhostCount++;
callback('DIRECT', 'localhost');
log.debug('ProxyResolver#resolveProxy localhost', url, 'DIRECT', stackText);
return;
}
const secureEndpoint = parsedUrl.protocol === 'https:';
const defaultPort = secureEndpoint ? 443 : 80;
// if there are any config entries present then env variables are ignored
let noProxyConfig = params.getNoProxyConfig ? params.getNoProxyConfig() : [];
if (noProxyConfig.length) {
let configNoProxy = noProxyFromConfig(noProxyConfig); // Not standardized.
if (typeof hostname === 'string' && configNoProxy(hostname, String(parsedUrl.port || defaultPort))) {
configNoProxyCount++;
callback('DIRECT', 'noProxyConfig');
log.debug('ProxyResolver#resolveProxy configNoProxy', url, 'DIRECT', stackText);
return;
}
}
else {
if (typeof hostname === 'string' && envNoProxy(hostname, String(parsedUrl.port || defaultPort))) {
envNoProxyCount++;
callback('DIRECT', 'noProxyEnv');
log.debug('ProxyResolver#resolveProxy envNoProxy', url, 'DIRECT', stackText);
return;
}
}
let settingsProxy = proxyFromConfigURL(getProxyURL());
if (settingsProxy) {
settingsCount++;
callback(settingsProxy, 'setting');
log.debug('ProxyResolver#resolveProxy settings', url, settingsProxy, stackText);
return;
}
if (envProxy) {
envCount++;
callback(envProxy, 'env');
log.debug('ProxyResolver#resolveProxy env', url, envProxy, stackText);
return;
}
if (!params.isUseHostProxyEnabled()) {
callback('DIRECT', 'remote');
log.debug('ProxyResolver#resolveProxy unconfigured', url, 'DIRECT', stackText);
return;
}
const key = getCacheKey(parsedUrl);
const proxy = getCachedProxy(key);
if (proxy) {
cacheCount++;
if (req) {
collectResult(results, proxy, secureEndpoint ? 'HTTPS' : 'HTTP', req);
}
callback(proxy, 'system_cached');
log.debug('ProxyResolver#resolveProxy cached', url, proxy, stackText);
return;
}
const start = Date.now();
params.resolveProxy(url) // Use full URL to ensure it is an actually used one.
.then(proxy => {
if (proxy) {
cacheProxy(key, proxy);
if (req) {
collectResult(results, proxy, secureEndpoint ? 'HTTPS' : 'HTTP', req);
}
}
callback(proxy, 'system');
log.debug('ProxyResolver#resolveProxy', url, proxy, stackText);
}).then(() => {
count++;
duration = Date.now() - start + duration;
}, err => {
errorCount++;
const fallback = cache.values().next().value; // fall back to any proxy (https://github.com/microsoft/vscode/issues/122825)
callback(fallback, 'fallback');
log.error('ProxyResolver#resolveProxy', fallback, toErrorMessage(err), stackText);
});
}
return {
resolveProxyWithRequest,
resolveProxyURL: (url) => new Promise((resolve, reject) => {
useProxySettings(url, undefined, '', result => {
try {
resolve((0, agent_1.getProxyURLFromResolverResult)(result).url);
}
catch (err) {
reject(err);
}
});
}),
resolveProxyByURL: (url) => new Promise((resolve, reject) => {
useProxySettings(url, undefined, '', (result, source) => {
try {
const { url: proxyURL, type } = (0, agent_1.getProxyURLFromResolverResult)(result);
resolve({ url: proxyURL, type, source });
}
catch (err) {
reject(err);
}
});
}),
};
}
exports.createProxyResolver = createProxyResolver;
function collectResult(results, resolveProxy, connection, req) {
const proxy = resolveProxy ? String(resolveProxy).trim().split(/\s+/, 1)[0] : 'EMPTY';
req.on('response', res => {
const code = `HTTP_${res.statusCode}`;
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
req.on('error', err => {
const code = err && typeof err.code === 'string' && err.code || 'UNKNOWN_ERROR';
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
}
function findOrCreateResult(results, proxy, connection, code) {
for (const result of results) {
if (result.proxy === proxy && result.connection === connection && result.code === code) {
return result;
}
}
const result = { proxy, connection, code, count: 0 };
results.push(result);
return result;
}
function proxyFromConfigURL(configURL) {
if (!configURL) {
return undefined;
}
const url = (configURL || '').trim();
const i = url.indexOf('://');
if (i === -1) {
return undefined;
}
const scheme = url.substr(0, i).toLowerCase();
const proxy = url.substr(i + 3);
if (scheme === 'http') {
return 'PROXY ' + proxy;
}
else if (scheme === 'https') {
return 'HTTPS ' + proxy;
}
else if (scheme === 'socks' || scheme === 'socks5' || scheme === 'socks5h') {
return 'SOCKS ' + proxy;
}
else if (scheme === 'socks4' || scheme === 'socks4a') {
return 'SOCKS4 ' + proxy;
}
return undefined;
}
function shouldBypassProxy(value) {
if (value.includes("*")) {
return () => true;
}
const filters = value
.map(s => s.trim().split(':', 2))
.map(([name, port]) => ({ name, port }))
.filter(filter => !!filter.name)
.map(({ name, port }) => {
const domain = name[0] === '.' ? name : `.${name}`;
return { domain, port };
});
if (!filters.length) {
return () => false;
}
return (hostname, port) => filters.some(({ domain, port: filterPort }) => {
return `.${hostname.toLowerCase()}`.endsWith(domain) && (!filterPort || port === filterPort);
});
}
function noProxyFromEnv(envValue) {
const value = (envValue || '')
.trim()
.toLowerCase()
.split(',');
return shouldBypassProxy(value);
}
function noProxyFromConfig(noProxy) {
const value = noProxy
.map((item) => item.trim().toLowerCase());
return shouldBypassProxy(value);
}
function createHttpPatch(params, originals, resolveProxy) {
return {
get: patch(originals.get),
request: patch(originals.request)
};
function patch(original) {
function patched(url, options, callback) {
if (typeof url !== 'string' && !(url && url.searchParams)) {
callback = options;
options = url;
url = null;
}
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (options.socketPath) {
return original.apply(null, arguments);
}
const originalAgent = options.agent;
if (originalAgent === true) {
throw new Error('Unexpected agent option: true');
}
const isHttps = originals.globalAgent.protocol === 'https:';
const optionsPatched = originalAgent instanceof agent_1.PacProxyAgent;
const config = params.getProxySupport();
const useProxySettings = !optionsPatched && (config === 'override' || config === 'fallback' || (config === 'on' && originalAgent === undefined));
// If Agent.options.ca is set to undefined, it overwrites RequestOptions.ca.
const originalOptionsCa = isHttps ? options.ca : undefined;
const originalAgentCa = isHttps && originalAgent instanceof originals.Agent && originalAgent.options && 'ca' in originalAgent.options && originalAgent.options.ca;
const originalCa = originalAgentCa !== false ? originalAgentCa : originalOptionsCa;
const addCertificatesV1 = !optionsPatched && params.addCertificatesV1() && isHttps && !originalCa;
if (useProxySettings || addCertificatesV1) {
if (url) {
const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url;
const urlOptions = {
protocol: parsed.protocol,
hostname: parsed.hostname.lastIndexOf('[', 0) === 0 ? parsed.hostname.slice(1, -1) : parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`
};
if (parsed.username || parsed.password) {
options.auth = `${parsed.username}:${parsed.password}`;
}
options = Object.assign(Object.assign({}, urlOptions), options);
}
else {
options = Object.assign({}, options);
}
const resolveP = (req, opts, url) => new Promise(resolve => resolveProxy({ useProxySettings, addCertificatesV1, testCertificates: options.testCertificates }, req, opts, url, resolve));
const host = options.hostname || options.host;
const isLocalhost = !host || host === 'localhost' || host === '127.0.0.1'; // Avoiding https://github.com/microsoft/vscode/issues/120354
const agent = (0, agent_1.createPacProxyAgent)(resolveP, {
originalAgent: (!useProxySettings || isLocalhost || config === 'fallback') ? originalAgent : undefined,
lookupProxyAuthorization: params.lookupProxyAuthorization,
// keepAlive: ((originalAgent || originals.globalAgent) as { keepAlive?: boolean }).keepAlive, // Skipping due to https://github.com/microsoft/vscode/issues/228872.
_vscodeTestReplaceCaCerts: options._vscodeTestReplaceCaCerts,
}, opts => new Promise(resolve => addCertificatesToOptionsV1(params, params.addCertificatesV1(), opts, resolve, options.testCertificates)));
agent.protocol = isHttps ? 'https:' : 'http:';
options.agent = agent;
if (isHttps) {
options.ca = originalCa;
}
return original(options, callback);
}
return original.apply(null, arguments);
}
return patched;
}
}
exports.createHttpPatch = createHttpPatch;
function createNetPatch(params, originals) {
return {
connect: patchNetConnect(params, originals.connect),
};
}
exports.createNetPatch = createNetPatch;
function patchNetConnect(params, original) {
function connect(...args) {
if (params.getLogLevel() === LogLevel.Trace) {
params.log.trace('ProxyResolver#net.connect', toLogString(args));
}
if (!params.addCertificatesV2()) {
return original.apply(null, arguments);
}
const socket = new net.Socket();
socket.connecting = true;
getOrLoadAdditionalCertificates(params)
.then(() => {
const options = args.find(arg => arg && typeof arg === 'object');
if (options === null || options === void 0 ? void 0 : options.timeout) {
socket.setTimeout(options.timeout);
}
socket.connect.apply(socket, arguments);
})
.catch(err => {
params.log.error('ProxyResolver#net.connect', toErrorMessage(err));
});
return socket;
}
return connect;
}
function createTlsPatch(params, originals) {
return {
connect: patchTlsConnect(params, originals.connect),
createSecureContext: patchCreateSecureContext(originals.createSecureContext),
};
}
exports.createTlsPatch = createTlsPatch;
function patchTlsConnect(params, original) {
function connect(...args) {
var _a;
if (params.getLogLevel() === LogLevel.Trace) {
params.log.trace('ProxyResolver#tls.connect', toLogString(args));
}
let options = args.find(arg => arg && typeof arg === 'object');
if (!params.addCertificatesV2() || (options === null || options === void 0 ? void 0 : options.ca)) {
return original.apply(null, arguments);
}
const testCerts = options === null || options === void 0 ? void 0 : options.testCertificates;
let secureConnectListener = args.find(arg => typeof arg === 'function');
if (!options) {
options = {};
const listenerIndex = args.findIndex(arg => typeof arg === 'function');
if (listenerIndex !== -1) {
args[listenerIndex - 1] = options;
}
else {
args[2] = options;
}
}
else {
options = Object.assign({}, options);
}
const port = typeof args[0] === 'number' ? args[0]
: typeof args[0] === 'string' && !isNaN(Number(args[0])) ? Number(args[0]) // E.g., http2 module passes port as string.
: options.port;
const host = typeof args[1] === 'string' ? args[1] : options.host;
let tlsSocket;
if (options.socket) {
if (!options.secureContext) {
options.secureContext = tls.createSecureContext(options);
}
const certificates = (_a = _certs.get(!!params.loadSystemCertificatesFromNode())) === null || _a === void 0 ? void 0 : _a.result;
if (!certificates) {
params.log.trace('ProxyResolver#tls.connect waiting for existing socket connect');
options.socket.once('connect', () => {
var _a;
params.log.trace('ProxyResolver#tls.connect got existing socket connect - adding certs');
for (const cert of ((_a = _certs.get(!!params.loadSystemCertificatesFromNode())) === null || _a === void 0 ? void 0 : _a.result) || []) {
options.secureContext.context.addCACert(cert);
}
if (testCerts) {
for (const cert of testCerts) {
options.secureContext.context.addCACert(cert);
}
}
});
}
else {
params.log.trace('ProxyResolver#tls.connect existing socket already connected - adding certs');
for (const cert of certificates) {
options.secureContext.context.addCACert(cert);
}
if (testCerts) {
for (const cert of testCerts) {
options.secureContext.context.addCACert(cert);
}
}
}
}
else {
if (!options.secureContext) {
options.secureContext = tls.createSecureContext(options);
}
params.log.trace('ProxyResolver#tls.connect creating unconnected socket');
const socket = options.socket = new net.Socket();
socket.connecting = true;
getOrLoadAdditionalCertificates(params)
.then(caCertificates => {
params.log.trace('ProxyResolver#tls.connect adding certs before connecting socket');
for (const cert of caCertificates) {
options.secureContext.context.addCACert(cert);
}
if (testCerts) {
for (const cert of testCerts) {
options.secureContext.context.addCACert(cert);
}
}
if (options === null || options === void 0 ? void 0 : options.timeout) {
socket.setTimeout(options.timeout);
socket.once('timeout', () => {
tlsSocket.emit('timeout');
});
}
socket.connect(Object.assign({ port: port, host }, options));
})
.catch(err => {
params.log.error('ProxyResolver#tls.connect', toErrorMessage(err));
});
}
if (typeof args[1] === 'string') {
tlsSocket = original(port, host, options, secureConnectListener);
}
else if (typeof args[0] === 'number' || typeof args[0] === 'string' && !isNaN(Number(args[0]))) {
tlsSocket = original(port, options, secureConnectListener);
}
else {
tlsSocket = original(options, secureConnectListener);
}
return tlsSocket;
}
return connect;
}
function patchCreateSecureContext(original) {
return function (details) {
const context = original.apply(null, arguments);
const certs = details === null || details === void 0 ? void 0 : details._vscodeAdditionalCaCerts;
if (certs) {
for (const cert of certs) {
context.context.addCACert(cert);
}
}
return context;
};
}
function createFetchPatch(params, originalFetch, resolveProxyURL, options) {
const interceptors = options === null || options === void 0 ? void 0 : options.interceptors;
const hasInterceptors = !!interceptors && interceptors.length > 0;
// Caches the composed dispatcher per base agent so all requests share the
// same composed dispatcher.
const composedAgentCache = hasInterceptors ? new WeakMap() : undefined;
function withInterceptors(agent) {
if (!agent || !composedAgentCache) {
return agent;
}
let composed = composedAgentCache.get(agent);
if (!composed) {
composed = agent.compose(...interceptors);
composedAgentCache.set(agent, composed);
}
return composed;
}
return function patchedFetch(input, init) {
return __awaiter(this, void 0, void 0, function* () {
if (!params.isAdditionalFetchSupportEnabled()) {
return originalFetch(input, init);
}
const agentOptions = getAgentOptions(init);
if (agentOptions.socketPath) {
return originalFetch(input, init);
}
const proxySupport = params.getProxySupport();
const doResolveProxy = proxySupport === 'override' || proxySupport === 'fallback' || (proxySupport === 'on' && (init === null || init === void 0 ? void 0 : init.dispatcher) === undefined);
const addCerts = params.addCertificatesV1() || params.addCertificatesV2(); // There is no v2 for `fetch`, checking both settings.
if (!doResolveProxy && !addCerts) {
return originalFetch(input, init);
}
const urlString = typeof input === 'string' ? input : 'cache' in input ? input.url : input.toString();
const proxyURL = doResolveProxy ? yield resolveProxyURL(urlString) : undefined;
if (!proxyURL && !addCerts) {
return originalFetch(input, init);
}
const systemCA = addCerts ? [...tls.rootCertificates, ...yield getOrLoadAdditionalCertificates(params)] : undefined;
const { allowH2 } = agentOptions;
const requestCA = agentOptions.requestCA || systemCA;
const proxyCA = agentOptions.proxyCA || systemCA;
if (!proxyURL) {
const modifiedInit = Object.assign(Object.assign({}, init), { dispatcher: withInterceptors(getAgent(agentOptions.dispatcher, allowH2, requestCA, addCerts)) });
return originalFetch(input, modifiedInit);
}
const modifiedInit = Object.assign(Object.assign({}, init), { dispatcher: withInterceptors(getProxyAgent(params, agentOptions.dispatcher, proxyURL, allowH2, requestCA, proxyCA, addCerts)) });
return originalFetch(input, modifiedInit);
});
};
}
exports.createFetchPatch = createFetchPatch;
let previousAddCertsAgent = undefined;
let defaultAgent = undefined;
let agentCache = new WeakMap();
function getAgent(originalDispatcher, allowH2, requestCA, currentAddCerts) {
if (previousAddCertsAgent !== currentAddCerts) {
previousAddCertsAgent = currentAddCerts;
defaultAgent = undefined;
agentCache = new WeakMap();
}
if (!originalDispatcher) {
if (!defaultAgent) {
defaultAgent = createAgent(allowH2, requestCA);
}
return defaultAgent;
}
if (!agentCache.has(originalDispatcher)) {
agentCache.set(originalDispatcher, createAgent(allowH2, requestCA));
}
return agentCache.get(originalDispatcher);
}
function createAgent(allowH2, requestCA) {
return new undici.Agent({
allowH2,
connect: { ca: requestCA },
});
}
let previousAddCertsProxyAgent = undefined;
let previousLookupProxyAuthorization = undefined;
let defaultProxyAgents = new Map();
let proxyAgentCache = new WeakMap();
function getProxyAgent(params, originalDispatcher, proxyURL, allowH2, requestCA, proxyCA, currentAddCerts) {
const shouldClearCache = previousAddCertsProxyAgent !== currentAddCerts || previousLookupProxyAuthorization !== params.lookupProxyAuthorization;
if (shouldClearCache) {
previousAddCertsProxyAgent = currentAddCerts;
previousLookupProxyAuthorization = params.lookupProxyAuthorization;
defaultProxyAgents = new Map();
proxyAgentCache = new WeakMap();
}
if (!originalDispatcher) {
if (!defaultProxyAgents.has(proxyURL)) {
defaultProxyAgents.set(proxyURL, createProxyAgent(params, proxyURL, allowH2, requestCA, proxyCA));
}
return defaultProxyAgents.get(proxyURL);
}
let dispatcherCache = proxyAgentCache.get(originalDispatcher);
if (!dispatcherCache) {
dispatcherCache = new Map();
proxyAgentCache.set(originalDispatcher, dispatcherCache);
}
if (!dispatcherCache.has(proxyURL)) {
dispatcherCache.set(proxyURL, createProxyAgent(params, proxyURL, allowH2, requestCA, proxyCA));
}
return dispatcherCache.get(proxyURL);
}
function createProxyAgent(params, proxyURL, allowH2, requestCA, proxyCA) {
return new undici.ProxyAgent({
uri: proxyURL,
allowH2,
requestTls: requestCA ? { allowH2, ca: requestCA } : { allowH2 },
proxyTls: proxyCA ? { allowH2, ca: proxyCA } : { allowH2 },
clientFactory: (origin, opts) => new undici.Pool(origin, opts).compose((dispatch) => {
class ProxyAuthHandler extends undici.DecoratorHandler {
constructor(dispatch, options, handler, state) {
super(handler);
this.dispatch = dispatch;
this.options = options;
this.handler = handler;
this.state = state;
}
forwardResponseError(controller, err) {
var _a, _b;
if (this.connectResponseHeaders) {
err.connectResponseHeaders = this.connectResponseHeaders;
}
if (this.connectResponseStatusCode !== undefined) {
err.connectResponseStatusCode = this.connectResponseStatusCode;
}
(_b = (_a = this.handler).onResponseError) === null || _b === void 0 ? void 0 : _b.call(_a, controller, err);
}
onResponseError(controller, err) {
if (!(err instanceof ProxyAuthError)) {
return this.forwardResponseError(controller, err);
}
(() => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const proxyAuthorization = yield ((_a = params.lookupProxyAuthorization) === null || _a === void 0 ? void 0 : _a.call(params, proxyURL, err.proxyAuthenticate, this.state));
if (proxyAuthorization) {
setProxyAuthorizationHeader(this.options, proxyAuthorization);
this.dispatch(this.options, this);
}
else {
this.forwardResponseError(controller, new undici.errors.RequestAbortedError(`Proxy response (407) ?.== 200 when HTTP Tunneling`)); // Mimick undici's behavior
}
}
catch (err) {
this.forwardResponseError(controller, err);
}
}))();
}
onRequestUpgrade(controller, statusCode, headers, socket) {
var _a, _b;
this.connectResponseHeaders = headers;
this.connectResponseStatusCode = statusCode;
if (statusCode === 407 && headers) {
let proxyAuthenticate;
for (const header in headers) {
if (header.toLowerCase() === 'proxy-authenticate') {
proxyAuthenticate = headers[header];
break;
}
}
if (proxyAuthenticate) {
controller.abort(new ProxyAuthError(proxyAuthenticate));
return;
}
}
(_b = (_a = this.handler).onRequestUpgrade) === null || _b === void 0 ? void 0 : _b.call(_a, controller, statusCode, headers, socket);
}
}
return function proxyAuthDispatch(options, handler) {
const state = {};
(() => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const proxyAuthorization = yield ((_a = params.lookupProxyAuthorization) === null || _a === void 0 ? void 0 : _a.call(params, proxyURL, undefined, state));
if (proxyAuthorization) {
setProxyAuthorizationHeader(options, proxyAuthorization);
}
}
catch (err) {
// 407 handler will retry with auth.
params.log.error('ProxyResolver#proxyAuthDispatch lookupProxyAuthorization failed', toErrorMessage(err));
}
dispatch(options, new ProxyAuthHandler(dispatch, options, handler, state));
}))();
};
}),
});
}
function createWebSocketPatch(params, originalWebSocket, resolveProxyURL) {
const PatchedWebSocket = function WebSocket(url, protocols) {
if (!params.isWebSocketPatchEnabled()) {
return new originalWebSocket(url, protocols);
}
const proxySupport = params.getProxySupport();
const addCerts = params.addCertificatesV1() || params.addCertificatesV2();
let init;
if (protocols === undefined || protocols === null) {
init = {};
}
else if (typeof protocols === 'string' || Array.isArray(protocols)) {
init = { protocols };
}
else {
init = Object.assign({}, protocols);
}
const hasCallerDispatcher = init.dispatcher !== undefined;
const doResolveProxy = proxySupport === 'override' || proxySupport === 'fallback' || (proxySupport === 'on' && !hasCallerDispatcher);
if (!doResolveProxy && !addCerts) {
return new originalWebSocket(url, protocols);
}
const proxyDispatcher = new ProxyDispatcher(params, resolveProxyURL, doResolveProxy, addCerts, init.dispatcher);
init.dispatcher = proxyDispatcher;
const ws = new originalWebSocket(url, init);
Object.defineProperty(ws, 'responseHeaders', {
get() { return proxyDispatcher.responseHeaders; },
enumerable: true,
configurable: true,
});
Object.defineProperty(ws, 'responseStatusCode', {
get() { return proxyDispatcher.responseStatusCode; },
enumerable: true,
configurable: true,
});
Object.defineProperty(ws, 'responseStatusText', {
get() { return proxyDispatcher.responseStatusText; },
enumerable: true,
configurable: true,
});
Object.defineProperty(ws, 'networkError', {
get() { return proxyDispatcher.networkError; },
enumerable: true,
configurable: true,
});
return ws;
};
PatchedWebSocket.prototype = originalWebSocket.prototype;
Object.defineProperties(PatchedWebSocket, {
CONNECTING: { value: originalWebSocket.CONNECTING, enumerable: true },
OPEN: { value: originalWebSocket.OPEN, enumerable: true },
CLOSING: { value: originalWebSocket.CLOSING, enumerable: true },
CLOSED: { value: originalWebSocket.CLOSED, enumerable: true },
});
return PatchedWebSocket;
}
exports.createWebSocketPatch = createWebSocketPatch;
class ProxyDispatcher extends undici.Dispatcher {
constructor(params, resolveProxyURL, doResolveProxy, addCerts, originalDispatcher) {
super();
this.params = params;
this.resolveProxyURL = resolveProxyURL;
this.doResolveProxy = doResolveProxy;
this.addCerts = addCerts;
this.originalDispatcher = originalDispatcher;
}
dispatch(opts, handler) {
var _a;
const self = this;
function captureError(err) {
const connectHeaders = err.connectResponseHeaders;
if (connectHeaders) {
self.responseHeaders = convertHeaders(connectHeaders);
}
if (err.connectResponseStatusCode !== undefined) {
self.responseStatusCode = err.connectResponseStatusCode;
}
if (!self.networkError) {
self.networkError = err;
}
}
const wrappedHandler = Object.assign(Object.assign({}, handler), {
// Current API.
onResponseStart(controller, statusCode, headers, statusMessage) {
var _a;
self.responseStatusCode = statusCode;
self.responseStatusText = statusMessage;
self.responseHeaders = convertHeaders(headers);
return (_a = handler.onResponseStart) === null || _a === void 0 ? void 0 : _a.call(handler, controller, statusCode, headers, statusMessage);
},
onResponseError(controller, err) {
var _a;
captureError(err);
return (_a = handler.onResponseError) === null || _a === void 0 ? void 0 : _a.call(handler, controller, err);
},
onRequestUpgrade(controller, statusCode, headers, socket) {
var _a;
self.responseStatusCode = statusCode;
self.responseHeaders = convertHeaders(headers);
return (_a = handler.onRequestUpgrade) === null || _a === void 0 ? void 0 : _a.call(handler, controller, statusCode, headers, socket);
},
// Deprecated API still used by undici's internal core.
onHeaders(statusCode, rawHeaders, resume, statusText) {
var _a, _b, _c;
self.responseStatusCode = statusCode;
self.responseStatusText = statusText;
self.responseHeaders = parseRawHeaders(rawHeaders);
return (_c = (_b = (_a = handler).onHeaders) === null || _b === void 0 ? void 0 : _b.call(_a, statusCode, rawHeaders, resume, statusText)) !== null && _c !== void 0 ? _c : true;
},
onError(err) {
var _a, _b;
captureError(err);
return (_b = (_a = handler).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
},
onUpgrade(statusCode, rawHeaders, socket) {
var _a, _b;
self.responseStatusCode = statusCode;
if (rawHeaders) {
self.responseHeaders = parseRawHeaders(rawHeaders);
}
return (_b = (_a = handler).onUpgrade) === null || _b === void 0 ? void 0 : _b.call(_a, statusCode, rawHeaders, socket);
} });
const url = (_a = opts.origin) === null || _a === void 0 ? void 0 : _a.toString();
(() => __awaiter(this, void 0, void 0, function* () {
var _b;
try {
// Map ws:/wss: to http:/https: for proxy resolution
let resolveURL = url;
if (resolveURL) {
resolveURL = resolveURL.replace(/^ws(s?):/, 'http$1:');
}
const proxyURL = this.doResolveProxy && resolveURL ? yield this.resolveProxyURL(resolveURL) : undefined;
const systemCA = this.addCerts ? [...tls.rootCertificates, ...yield getOrLoadAdditionalCertificates(this.params)] : undefined;
const callerOptions = getAgentOptions({ dispatcher: this.originalDispatcher });
const requestCA = callerOptions.requestCA || systemCA;
const proxyCA = callerOptions.proxyCA || systemCA;
let dispatcher;
if (proxyURL) {
dispatcher = getProxyAgent(this.params, this.originalDispatcher, proxyURL, callerOptions.allowH2, requestCA, proxyCA, this.addCerts);
}
else if (this.addCerts) {
dispatcher = getAgent(this.originalDispatcher, callerOptions.allowH2, requestCA, this.addCerts);
}
else if (this.originalDispatcher) {
dispatcher = this.originalDispatcher;
}
else {
dispatcher = undici.getGlobalDispatcher();
}
dispatcher.dispatch(opts, wrappedHandler);
}
catch (err) {
(_b = handler.onResponseError) === null || _b === void 0 ? void 0 : _b.call(handler, null, err);
}
}))();
return true;
}
close() {
return Promise.resolve();
}
destroy() {
return Promise.resolve();
}
}
function parseRawHeaders(rawHeaders) {
const headers = {};
for (let i = 0; i + 1 < rawHeaders.length; i += 2) {
const key = rawHeaders[i].toString().toLowerCase();
const value = rawHeaders[i + 1].toString();
const existing = headers[key];
if (existing === undefined) {
headers[key] = value;
}
else if (Array.isArray(existing)) {
existing.push(value);
}
else {
headers[key] = [existing, value];
}
}
return headers;
}
function convertHeaders(headers) {
const result = {};
for (const key in headers) {
result[key.toLowerCase()] = headers[key];
}
return result;
}
/** @internal Exported for testing */
function setProxyAuthorizationHeader(options, proxyAuthorization) {
const headers = options.headers;
if (!headers) {
options.headers = ['Proxy-Authorization', proxyAuthorization];
}
else if (Array.isArray(headers) && (headers.length === 0 || Array.isArray(headers[0]))) {
// Tuple array format: [['Header1', 'value1'], ['Header2', 'value2'], ...]
const tuplesArray = headers;
const i = tuplesArray.findIndex(([key]) => key.toLowerCase() === 'proxy-authorization');
if (i === -1) {
tuplesArray.push(['Proxy-Authorization', proxyAuthorization]);
}
else {
tuplesArray[i][1] = proxyAuthorization;
}
}
else if (Array.isArray(headers)) {
// Flat array format: ['Header1', 'value1', 'Header2', 'value2', ...]
const i = headers.findIndex((value, index) => index % 2 === 0 && typeof value === 'string' && value.toLowerCase() === 'proxy-authorization');
if (i === -1) {
headers.push('Proxy-Authorization', proxyAuthorization);
}
else {
headers[i + 1] = proxyAuthorization;
}
}
else if (Symbol.iterator in headers) {
// Iterable of tuples format (e.g., Map): [['Header1', 'value1'], ['Header2', 'value2'], ...]
const headersArray = [...headers];
const i = headersArray.findIndex(([key]) => key.toLowerCase() === 'proxy-authorization');
if (i === -1) {
headersArray.push(['Proxy-Authorization', proxyAuthorization]);
}
else {
headersArray[i][1] = proxyAuthorization;
}
options.headers = headersArray;
}
else {
// Record format: { 'Header1': 'value1', 'Header2': 'value2', ... }
headers['Proxy-Authorization'] = proxyAuthorization;
}
}
exports.setProxyAuthorizationHeader = setProxyAuthorizationHeader;
class ProxyAuthError extends Error {
constructor(proxyAuthenticate) {
super('Proxy authentication required');
this.proxyAuthenticate = proxyAuthenticate;
}
}
const agentOptions = Symbol('agentOptions');
const proxyAgentOptions = Symbol('proxyAgentOptions');
function patchUndici(originalUndici) {
const originalAgent = originalUndici.Agent;
const patchedAgent = function PatchedAgent(opts) {
const agent = new originalAgent(opts);
agent[agentOptions] = Object.assign(Object.assign({}, opts), ((opts === null || opts === void 0 ? void 0 : opts.connect) && typeof (opts === null || opts === void 0 ? void 0 : opts.connect) === 'object' ? {