Skip to content

Commit f512687

Browse files
authored
fix(client-certificates): auto detect proxy in the browser (microsoft#36817)
1 parent d07e0b4 commit f512687

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { verifyClientCertificates } from './browserContext';
2626
import { createProxyAgent } from './utils/network';
2727
import { debugLogger } from './utils/debugLogger';
2828
import { createSocket, createTLSSocket } from './utils/happyEyeballs';
29+
import { getProxyForUrl } from '../utilsBundle';
2930

3031
import type * as types from './types';
3132
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from './utils/socksProxy';
@@ -99,8 +100,9 @@ class SocksProxyConnection {
99100
}
100101

101102
async connect() {
102-
if (this.socksProxy.proxyAgentFromOptions)
103-
this.target = await this.socksProxy.proxyAgentFromOptions.callback(new EventEmitter() as any, { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false });
103+
const proxyAgent = this.socksProxy.getProxyAgent(this.host, this.port);
104+
if (proxyAgent)
105+
this.target = await proxyAgent.callback(new EventEmitter() as any, { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false });
104106
else
105107
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
106108

@@ -242,15 +244,15 @@ export class ClientCertificatesProxy {
242244
ignoreHTTPSErrors: boolean | undefined;
243245
secureContextMap: Map<string, tls.SecureContext> = new Map();
244246
alpnCache: ALPNCache;
245-
proxyAgentFromOptions: ReturnType<typeof createProxyAgent>;
247+
private _proxy: types.ProxySettings | undefined;
246248

247249
private constructor(
248250
contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>
249251
) {
250252
verifyClientCertificates(contextOptions.clientCertificates);
251253
this.alpnCache = new ALPNCache();
252254
this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors;
253-
this.proxyAgentFromOptions = createProxyAgent(contextOptions.proxy);
255+
this._proxy = contextOptions.proxy;
254256
this._initSecureContexts(contextOptions.clientCertificates);
255257
this._socksProxy = new SocksProxy();
256258
this._socksProxy.setPattern('*');
@@ -274,6 +276,15 @@ export class ClientCertificatesProxy {
274276
loadDummyServerCertsIfNeeded();
275277
}
276278

279+
getProxyAgent(host: string, port: number) {
280+
const proxyFromOptions = createProxyAgent(this._proxy);
281+
if (proxyFromOptions)
282+
return proxyFromOptions;
283+
const proxyFromEnv = getProxyForUrl(`https://${host}:${port}`);
284+
if (proxyFromEnv)
285+
return createProxyAgent({ server: proxyFromEnv });
286+
}
287+
277288
_initSecureContexts(clientCertificates: types.BrowserContextOptions['clientCertificates']) {
278289
// Step 1. Group certificates by origin.
279290
const origin2certs = new Map<string, types.BrowserContextOptions['clientCertificates']>();

tests/library/client-certificates.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,49 @@ test.describe('browser', () => {
371371
await page.close();
372372
});
373373

374+
test('should pass with matching certificates and when a http proxy is used from env', async ({ browser, startCCServer, asset, browserName, proxyServer, isMac }) => {
375+
process.env.HTTPS_PROXY = `http://localhost:${proxyServer.PORT}`;
376+
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
377+
proxyServer.forwardTo(parseInt(new URL(serverURL).port, 10), { allowConnectRequests: true });
378+
const page = await browser.newPage({
379+
ignoreHTTPSErrors: true,
380+
clientCertificates: [{
381+
origin: new URL(serverURL).origin,
382+
certPath: asset('client-certificates/client/trusted/cert.pem'),
383+
keyPath: asset('client-certificates/client/trusted/key.pem'),
384+
}],
385+
});
386+
expect(proxyServer.connectHosts).toEqual([]);
387+
await page.goto(serverURL);
388+
const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1';
389+
expect([...new Set(proxyServer.connectHosts)]).toEqual([`${host}:${new URL(serverURL).port}`]);
390+
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
391+
await page.close();
392+
delete process.env.HTTPS_PROXY;
393+
});
394+
395+
test('should pass with matching certificates and when a http proxy is used from config but env is there', async ({ browser, startCCServer, asset, browserName, proxyServer, isMac }) => {
396+
process.env.HTTPS_PROXY = `http://this-should-not-taken-into-account:4242`;
397+
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
398+
proxyServer.forwardTo(parseInt(new URL(serverURL).port, 10), { allowConnectRequests: true });
399+
const page = await browser.newPage({
400+
ignoreHTTPSErrors: true,
401+
clientCertificates: [{
402+
origin: new URL(serverURL).origin,
403+
certPath: asset('client-certificates/client/trusted/cert.pem'),
404+
keyPath: asset('client-certificates/client/trusted/key.pem'),
405+
}],
406+
proxy: { server: `localhost:${proxyServer.PORT}` }
407+
});
408+
expect(proxyServer.connectHosts).toEqual([]);
409+
await page.goto(serverURL);
410+
const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1';
411+
expect([...new Set(proxyServer.connectHosts)]).toEqual([`${host}:${new URL(serverURL).port}`]);
412+
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
413+
await page.close();
414+
delete process.env.HTTPS_PROXY;
415+
});
416+
374417
test('should pass with matching certificates and when a socks proxy is used', async ({ browser, startCCServer, asset, browserName, isMac }) => {
375418
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
376419
const serverPort = parseInt(new URL(serverURL).port, 10);

0 commit comments

Comments
 (0)