Skip to content

Commit

Permalink
Move fetch patching to agent package (#228697)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrmarti committed Dec 2, 2024
1 parent fea0365 commit 608e07d
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 186 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.0",
"@vscode/policy-watcher": "^1.1.8",
"@vscode/proxy-agent": "^0.25.0",
"@vscode/proxy-agent": "^0.26.0",
"@vscode/ripgrep": "^1.15.9",
"@vscode/spdlog": "^0.15.0",
"@vscode/sqlite3": "5.1.8-vscode",
Expand Down Expand Up @@ -105,7 +105,6 @@
"node-pty": "^1.1.0-beta22",
"open": "^8.4.2",
"tas-client-umd": "0.2.0",
"undici": "^6.20.1",
"v8-inspect-profiler": "^0.1.1",
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
Expand Down
12 changes: 6 additions & 6 deletions remote/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@parcel/watcher": "2.1.0",
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.0",
"@vscode/proxy-agent": "^0.25.0",
"@vscode/proxy-agent": "^0.26.0",
"@vscode/ripgrep": "^1.15.9",
"@vscode/spdlog": "^0.15.0",
"@vscode/tree-sitter-wasm": "^0.0.4",
Expand All @@ -33,7 +33,6 @@
"native-watchdog": "^1.4.1",
"node-pty": "^1.1.0-beta22",
"tas-client-umd": "0.2.0",
"undici": "^6.20.1",
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "9.1.0",
Expand Down
177 changes: 7 additions & 170 deletions src/vs/workbench/api/node/proxyResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import { ExtHostExtensionService } from './extHostExtensionService.js';
import { URI } from '../../../base/common/uri.js';
import { ILogService, LogLevel as LogServiceLevel } from '../../../platform/log/common/log.js';
import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates, ResolveProxyWithRequest, getOrLoadAdditionalCertificates, LookupProxyAuthorization } from '@vscode/proxy-agent';
import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates, ResolveProxyWithRequest } from '@vscode/proxy-agent';
import { AuthInfo } from '../../../platform/request/common/request.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { createRequire } from 'node:module';
import type * as undiciType from 'undici-types';
import type * as tlsType from 'tls';
import type * as streamType from 'stream';
import { lookupKerberosAuthorization } from '../../../platform/request/node/requestService.js';
import * as proxyAgent from '@vscode/proxy-agent';

Expand All @@ -26,7 +25,6 @@ const http = require('http');
const https = require('https');
const tls: typeof tlsType = require('tls');
const net = require('net');
const undici: typeof undiciType = require('undici');

const systemCertificatesV2Default = false;
const useElectronFetchDefault = false;
Expand All @@ -49,6 +47,7 @@ export function connectProxyResolver(
getProxyURL: () => configProvider.getConfiguration('http').get('proxy'),
getProxySupport: () => configProvider.getConfiguration('http').get<ProxySupportSetting>('proxySupport') || 'off',
getNoProxyConfig: () => configProvider.getConfiguration('http').get<string[]>('noProxy') || [],
isAdditionalFetchSupportEnabled: () => configProvider.getConfiguration('http').get<boolean>('fetchAdditionalSupport', true),
addCertificatesV1: () => certSettingV1(configProvider),
addCertificatesV2: () => certSettingV2(configProvider),
log: extHostLogService,
Expand Down Expand Up @@ -94,7 +93,7 @@ export function connectProxyResolver(
const target = (proxyAgent as any).default || proxyAgent;
target.resolveProxyURL = resolveProxyURL;

patchGlobalFetch(configProvider, mainThreadTelemetry, initData, resolveProxyURL, params.lookupProxyAuthorization!, getOrLoadAdditionalCertificates.bind(undefined, params), disposables);
patchGlobalFetch(params, configProvider, mainThreadTelemetry, initData, resolveProxyURL, disposables);

const lookup = createPatchedModules(params, resolveProxyWithRequest);
return configureModuleLoading(extensionService, lookup);
Expand All @@ -112,11 +111,11 @@ const unsafeHeaders = [
'set-cookie',
];

function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelemetry: MainThreadTelemetryShape, initData: IExtensionHostInitData, resolveProxyURL: (url: string) => Promise<string | undefined>, lookupProxyAuthorization: LookupProxyAuthorization, loadAdditionalCertificates: () => Promise<string[]>, disposables: DisposableStore) {
function patchGlobalFetch(params: ProxyAgentParams, configProvider: ExtHostConfigProvider, mainThreadTelemetry: MainThreadTelemetryShape, initData: IExtensionHostInitData, resolveProxyURL: (url: string) => Promise<string | undefined>, disposables: DisposableStore) {
if (!(globalThis as any).__vscodeOriginalFetch) {
const originalFetch = globalThis.fetch;
(globalThis as any).__vscodeOriginalFetch = originalFetch;
const patchedFetch = patchFetch(originalFetch, configProvider, resolveProxyURL, lookupProxyAuthorization, loadAdditionalCertificates);
const patchedFetch = proxyAgent.createFetchPatch(params, originalFetch, resolveProxyURL);
(globalThis as any).__vscodePatchedFetch = patchedFetch;
let useElectronFetch = false;
if (!initData.remote.isRemote) {
Expand Down Expand Up @@ -152,7 +151,7 @@ function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelem
recordFetchFeatureUse(mainThreadTelemetry, 'integrity');
}
if (!useElectronFetch || isDataUrl || isBlobUrl || isManualRedirect || integrity) {
const response = await patchedFetch(input, init, urlString);
const response = await patchedFetch(input, init);
monitorResponseProperties(mainThreadTelemetry, response, urlString);
return response;
}
Expand All @@ -174,120 +173,6 @@ function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelem
}
}

function patchFetch(originalFetch: typeof globalThis.fetch, configProvider: ExtHostConfigProvider, resolveProxyURL: (url: string) => Promise<string | undefined>, lookupProxyAuthorization: LookupProxyAuthorization, loadAdditionalCertificates: () => Promise<string[]>) {
return async function patchedFetch(input: string | URL | Request, init?: RequestInit, urlString?: string) {
const config = configProvider.getConfiguration('http');
const enabled = config.get<boolean>('fetchAdditionalSupport');
if (!enabled) {
return originalFetch(input, init);
}
const proxySupport = config.get<ProxySupportSetting>('proxySupport') || 'off';
const doResolveProxy = proxySupport === 'override' || proxySupport === 'fallback' || (proxySupport === 'on' && ((init as any)?.dispatcher) === undefined);
const addCerts = config.get<boolean>('systemCertificates');
if (!doResolveProxy && !addCerts) {
return originalFetch(input, init);
}
if (!urlString) { // for testing
urlString = typeof input === 'string' ? input : 'cache' in input ? input.url : input.toString();
}
const proxyURL = doResolveProxy ? await resolveProxyURL(urlString) : undefined;
if (!proxyURL && !addCerts) {
return originalFetch(input, init);
}
const ca = addCerts ? [...tls.rootCertificates, ...await loadAdditionalCertificates()] : undefined;
const { allowH2, requestCA, proxyCA } = getAgentOptions(ca, init);
if (!proxyURL) {
const modifiedInit = {
...init,
dispatcher: new undici.Agent({
allowH2,
connect: { ca: requestCA },
})
};
return originalFetch(input, modifiedInit);
}

const state: Record<string, any> = {};
const proxyAuthorization = await lookupProxyAuthorization(proxyURL, undefined, state);
const modifiedInit = {
...init,
dispatcher: new undici.ProxyAgent({
uri: proxyURL,
allowH2,
headers: proxyAuthorization ? { 'Proxy-Authorization': proxyAuthorization } : undefined,
...(requestCA ? { requestTls: { ca: requestCA } } : {}),
...(proxyCA ? { proxyTls: { ca: proxyCA } } : {}),
clientFactory: (origin: URL, opts: object): undiciType.Dispatcher => (new undici.Pool(origin, opts) as any).compose((dispatch: undiciType.Dispatcher['dispatch']) => {
class ProxyAuthHandler extends undici.DecoratorHandler {
private abort: ((err?: Error) => void) | undefined;
constructor(private dispatch: undiciType.Dispatcher['dispatch'], private options: undiciType.Dispatcher.DispatchOptions, private handler: undiciType.Dispatcher.DispatchHandlers) {
super(handler);
}
onConnect(abort: (err?: Error) => void): void {
this.abort = abort;
this.handler.onConnect?.(abort);
}
onError(err: Error): void {
if (!(err instanceof ProxyAuthError)) {
return this.handler.onError?.(err);
}
(async () => {
try {
const proxyAuthorization = await lookupProxyAuthorization(proxyURL!, err.proxyAuthenticate, state);
if (proxyAuthorization) {
if (!this.options.headers) {
this.options.headers = ['Proxy-Authorization', proxyAuthorization];
} else if (Array.isArray(this.options.headers)) {
const i = this.options.headers.findIndex((value, index) => index % 2 === 0 && value.toLowerCase() === 'proxy-authorization');
if (i === -1) {
this.options.headers.push('Proxy-Authorization', proxyAuthorization);
} else {
this.options.headers[i + 1] = proxyAuthorization;
}
} else {
this.options.headers['Proxy-Authorization'] = proxyAuthorization;
}
this.dispatch(this.options, this);
} else {
this.handler.onError?.(new undici.errors.RequestAbortedError(`Proxy response (407) ?.== 200 when HTTP Tunneling`)); // Mimick undici's behavior
}
} catch (err) {
this.handler.onError?.(err);
}
})();
}
onUpgrade(statusCode: number, headers: Buffer[] | string[] | null, socket: streamType.Duplex): void {
if (statusCode === 407 && headers) {
const proxyAuthenticate: string[] = [];
for (let i = 0; i < headers.length; i += 2) {
if (headers[i].toString().toLowerCase() === 'proxy-authenticate') {
proxyAuthenticate.push(headers[i + 1].toString());
}
}
if (proxyAuthenticate.length) {
this.abort?.(new ProxyAuthError(proxyAuthenticate));
return;
}
}
this.handler.onUpgrade?.(statusCode, headers, socket);
}
}
return function proxyAuthDispatch(options: undiciType.Dispatcher.DispatchOptions, handler: undiciType.Dispatcher.DispatchHandlers) {
return dispatch(options, new ProxyAuthHandler(dispatch, options, handler));
};
}),
})
};
return originalFetch(input, modifiedInit);
};
}

class ProxyAuthError extends Error {
constructor(public proxyAuthenticate: string[]) {
super('Proxy authentication required');
}
}

function monitorResponseProperties(mainThreadTelemetry: MainThreadTelemetryShape, response: Response, urlString: string) {
const originalUrl = response.url;
Object.defineProperty(response, 'url', {
Expand Down Expand Up @@ -401,7 +286,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku
if (!cache[request]) {
if (request === 'undici') {
const undici = original.apply(this, arguments);
patchUndici(undici);
proxyAgent.patchUndici(undici);
cache[request] = undici;
} else {
const mod = lookup[request];
Expand All @@ -413,54 +298,6 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku
});
}

const agentOptions = Symbol('agentOptions');
const proxyAgentOptions = Symbol('proxyAgentOptions');

function patchUndici(undici: typeof undiciType) {
const originalAgent = undici.Agent;
const patchedAgent = function PatchedAgent(opts?: undiciType.Agent.Options): undiciType.Agent {
const agent = new originalAgent(opts);
(agent as any)[agentOptions] = {
...opts,
...(opts?.connect && typeof opts?.connect === 'object' ? { connect: { ...opts.connect } } : undefined),
};
return agent;
};
patchedAgent.prototype = originalAgent.prototype;
(undici as any).Agent = patchedAgent;

const originalProxyAgent = undici.ProxyAgent;
const patchedProxyAgent = function PatchedProxyAgent(opts: undiciType.ProxyAgent.Options | string): undiciType.ProxyAgent {
const proxyAgent = new originalProxyAgent(opts);
(proxyAgent as any)[proxyAgentOptions] = typeof opts === 'string' ? opts : {
...opts,
...(opts?.connect && typeof opts?.connect === 'object' ? { connect: { ...opts.connect } } : undefined),
};
return proxyAgent;
};
patchedProxyAgent.prototype = originalProxyAgent.prototype;
(undici as any).ProxyAgent = patchedProxyAgent;
}

function getAgentOptions(systemCA: string[] | undefined, requestInit: RequestInit | undefined) {
let allowH2: boolean | undefined;
let requestCA: string | Buffer | Array<string | Buffer> | undefined = systemCA;
let proxyCA: string | Buffer | Array<string | Buffer> | undefined = systemCA;
const dispatcher: undiciType.Dispatcher = (requestInit as any)?.dispatcher;
const originalAgentOptions: undiciType.Agent.Options | undefined = dispatcher && (dispatcher as any)[agentOptions];
if (originalAgentOptions) {
allowH2 = originalAgentOptions.allowH2;
requestCA = originalAgentOptions.connect && typeof originalAgentOptions.connect === 'object' && 'ca' in originalAgentOptions.connect && originalAgentOptions.connect.ca || systemCA;
}
const originalProxyAgentOptions: undiciType.ProxyAgent.Options | string | undefined = dispatcher && (dispatcher as any)[proxyAgentOptions];
if (originalProxyAgentOptions && typeof originalProxyAgentOptions === 'object') {
allowH2 = originalProxyAgentOptions.allowH2;
requestCA = originalProxyAgentOptions.requestTls && 'ca' in originalProxyAgentOptions.requestTls && originalProxyAgentOptions.requestTls.ca || systemCA;
proxyCA = originalProxyAgentOptions.proxyTls && 'ca' in originalProxyAgentOptions.proxyTls && originalProxyAgentOptions.proxyTls.ca || systemCA;
}
return { allowH2, requestCA, proxyCA };
}

async function lookupProxyAuthorization(
extHostWorkspace: IExtHostWorkspaceProvider,
extHostLogService: ILogService,
Expand Down

0 comments on commit 608e07d

Please sign in to comment.