diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 7e8b96839f4..61828a784fb 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -112,6 +112,13 @@ "farboo.get": { "type": "string", "default": "get-prop" + }, + "integration-test.http.proxy": { + "type": "string" + }, + "integration-test.http.proxyAuth": { + "type": "string", + "default": "get-prop" } } }, @@ -251,7 +258,8 @@ "@types/mocha": "^9.1.1", "@types/node": "20.x", "@types/node-forge": "^1.3.11", - "node-forge": "^1.3.1" + "node-forge": "^1.3.1", + "straightforward": "^4.2.2" }, "repository": { "type": "git", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts index 60f100c7c1f..ccda85b442a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts @@ -5,12 +5,14 @@ import * as https from 'https'; import 'mocha'; -import { assertNoRpc } from '../utils'; +import { assertNoRpc, delay } from '../utils'; import { pki } from 'node-forge'; import { AddressInfo } from 'net'; import { resetCaches } from '@vscode/proxy-agent'; +import * as vscode from 'vscode'; +import { middleware, Straightforward } from 'straightforward'; -suite('vscode API - network proxy support', () => { +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('vscode API - network proxy support', () => { teardown(async function () { assertNoRpc(); @@ -74,4 +76,76 @@ suite('vscode API - network proxy support', () => { server.close(); } }); + + test('basic auth', async () => { + const url = 'https://example.com'; // Need to use non-local URL because local URLs are excepted from proxying. + const user = 'testuser'; + const pass = 'testpassword'; + + const sf = new Straightforward(); + let authEnabled = false; + const auth = middleware.auth({ user, pass }); + sf.onConnect.use(async (context, next) => { + if (authEnabled) { + return auth(context, next); + } + next(); + }); + sf.onConnect.use(({ clientSocket }) => { + // Shortcircuit the request. + if (authEnabled) { + clientSocket.end('HTTP/1.1 204\r\n\r\n'); + } else { + clientSocket.end('HTTP/1.1 418\r\n\r\n'); + } + }); + const proxyListen = sf.listen(0); + + try { + await proxyListen; + const proxyPort = (sf.server.address() as AddressInfo).port; + + await vscode.workspace.getConfiguration().update('integration-test.http.proxy', `PROXY 127.0.0.1:${proxyPort}`, vscode.ConfigurationTarget.Global); + await delay(1000); // Wait for the configuration change to propagate. + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 418) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 418): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + + authEnabled = true; + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 407) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 407): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + + await vscode.workspace.getConfiguration().update('integration-test.http.proxyAuth', `${user}:${pass}`, vscode.ConfigurationTarget.Global); + await delay(1000); // Wait for the configuration change to propagate. + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 204) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 204): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + } finally { + sf.close(); + await vscode.workspace.getConfiguration().update('integration-test.http.proxy', undefined, vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration().update('integration-test.http.proxyAuth', undefined, vscode.ConfigurationTarget.Global); + } + }); }); diff --git a/extensions/vscode-api-tests/yarn.lock b/extensions/vscode-api-tests/yarn.lock index 33a2f511927..9999a40aa14 100644 --- a/extensions/vscode-api-tests/yarn.lock +++ b/extensions/vscode-api-tests/yarn.lock @@ -28,12 +28,138 @@ dependencies: undici-types "~5.26.4" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + node-forge@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +straightforward@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/straightforward/-/straightforward-4.2.2.tgz#a7d99b313dec5c04b0c637c7b8684dd44dc9167c" + integrity sha512-MxfuNnyTP4RPjadI3DkYIcNIp0DMXeDmAXY4/6QivU8lLIPGUqaS5VsEkaQ2QC+FICzc7QTb/lJPRIhGRKVuMA== + dependencies: + debug "^4.3.4" + yargs "^17.6.2" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" diff --git a/src/vs/platform/native/electron-main/auth.ts b/src/vs/platform/native/electron-main/auth.ts index da7e0ea26cc..15918242bba 100644 --- a/src/vs/platform/native/electron-main/auth.ts +++ b/src/vs/platform/native/electron-main/auth.ts @@ -12,6 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; @@ -54,6 +55,7 @@ export class ProxyAuthService extends Disposable implements IProxyAuthService { @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService, @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, ) { super(); @@ -135,6 +137,26 @@ export class ProxyAuthService extends Disposable implements IProxyAuthService { private async doResolveProxyCredentials(authInfo: AuthInfo, authInfoHash: string): Promise { this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo); + // For testing. + if (this.environmentMainService.extensionTestsLocationURI) { + const credentials = this.configurationService.getValue('integration-test.http.proxyAuth'); + if (credentials) { + const j = credentials.indexOf(':'); + if (j !== -1) { + return { + username: credentials.substring(0, j), + password: credentials.substring(j + 1) + }; + } else { + return { + username: credentials, + password: '' + }; + } + } + return undefined; + } + // Reply with manually supplied credentials. Fail if they are wrong. const newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() @@ -145,8 +167,10 @@ export class ProxyAuthService extends Disposable implements IProxyAuthService { const i = uri.authority.indexOf('@'); if (i !== -1) { if (authInfo.attempt > 1) { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - ignoring previously used config/envvar credentials'); return undefined; // We tried already, let the user handle it. } + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found config/envvar credentials to use'); const credentials = uri.authority.substring(0, i); const j = credentials.indexOf(':'); if (j !== -1) { diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 3b49b725dd9..e3013c06f8c 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -808,6 +808,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { + if (this.environmentMainService.extensionTestsLocationURI) { + const testProxy = this.configurationService.getValue('integration-test.http.proxy'); + if (testProxy) { + return testProxy; + } + } const window = this.codeWindowById(windowId); const session = window?.win?.webContents?.session;