mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
fix(client-certificates): return target errors on response when using http2 (#31867)
This commit is contained in:
parent
335d31bf65
commit
09581b615d
@ -16,6 +16,7 @@
|
||||
|
||||
import net from 'net';
|
||||
import path from 'path';
|
||||
import http2 from 'http2';
|
||||
import type https from 'https';
|
||||
import fs from 'fs';
|
||||
import tls from 'tls';
|
||||
@ -80,17 +81,19 @@ class SocksProxyConnection {
|
||||
target!: net.Socket;
|
||||
// In case of http, we just pipe data to the target socket and they are |undefined|.
|
||||
internal: stream.Duplex | undefined;
|
||||
private _targetCloseEventListener: () => void;
|
||||
|
||||
constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) {
|
||||
this.socksProxy = socksProxy;
|
||||
this.uid = uid;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this._targetCloseEventListener = () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid });
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
|
||||
this.target.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }));
|
||||
this.target.on('close', this._targetCloseEventListener);
|
||||
this.target.on('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }));
|
||||
this.socksProxy._socksProxy.socketConnected({
|
||||
uid: this.uid,
|
||||
@ -139,7 +142,6 @@ class SocksProxyConnection {
|
||||
dummyServer.emit('connection', this.internal);
|
||||
dummyServer.on('secureConnection', internalTLS => {
|
||||
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`);
|
||||
internalTLS.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }));
|
||||
const tlsOptions: tls.ConnectionOptions = {
|
||||
socket: this.target,
|
||||
host: this.host,
|
||||
@ -154,8 +156,10 @@ class SocksProxyConnection {
|
||||
tlsOptions.ca = [fs.readFileSync(process.env.PWTEST_UNSUPPORTED_CUSTOM_CA)];
|
||||
const targetTLS = tls.connect(tlsOptions);
|
||||
|
||||
internalTLS.pipe(targetTLS);
|
||||
targetTLS.pipe(internalTLS);
|
||||
targetTLS.on('secureConnect', () => {
|
||||
internalTLS.pipe(targetTLS);
|
||||
targetTLS.pipe(internalTLS);
|
||||
});
|
||||
|
||||
// Handle close and errors
|
||||
const closeBothSockets = () => {
|
||||
@ -169,11 +173,30 @@ class SocksProxyConnection {
|
||||
internalTLS.on('error', () => closeBothSockets());
|
||||
targetTLS.on('error', error => {
|
||||
debugLogger.log('client-certificates', `error when connecting to target: ${error.message}`);
|
||||
const responseBody = 'Playwright client-certificate error: ' + error.message;
|
||||
if (internalTLS?.alpnProtocol === 'h2') {
|
||||
// https://github.com/nodejs/node/issues/46152
|
||||
// TODO: http2.performServerHandshake does not work here for some reason.
|
||||
// This method is available only in Node.js 20+
|
||||
if ('performServerHandshake' in http2) {
|
||||
// In case of an 'error' event on the target connection, we still need to perform the http2 handshake on the browser side.
|
||||
// This is an async operation, so we need to intercept the close event to prevent the socket from being closed too early.
|
||||
this.target.removeListener('close', this._targetCloseEventListener);
|
||||
// @ts-expect-error
|
||||
const session: http2.ServerHttp2Session = http2.performServerHandshake(internalTLS);
|
||||
session.on('stream', (stream: http2.ServerHttp2Stream) => {
|
||||
stream.respond({
|
||||
'content-type': 'text/html',
|
||||
[http2.constants.HTTP2_HEADER_STATUS]: 503,
|
||||
});
|
||||
stream.end(responseBody, () => {
|
||||
session.close();
|
||||
closeBothSockets();
|
||||
});
|
||||
stream.on('error', () => closeBothSockets());
|
||||
});
|
||||
} else {
|
||||
closeBothSockets();
|
||||
}
|
||||
} else {
|
||||
const responseBody = 'Playwright client-certificate error: ' + error.message;
|
||||
internalTLS.end([
|
||||
'HTTP/1.1 503 Internal Server Error',
|
||||
'Content-Type: text/html; charset=utf-8',
|
||||
@ -181,8 +204,8 @@ class SocksProxyConnection {
|
||||
'\r\n',
|
||||
responseBody,
|
||||
].join('\r\n'));
|
||||
closeBothSockets();
|
||||
}
|
||||
closeBothSockets();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -25,6 +25,7 @@ const { createHttpsServer, createHttp2Server } = require('../../packages/playwri
|
||||
type TestOptions = {
|
||||
startCCServer(options?: {
|
||||
http2?: boolean;
|
||||
enableHTTP1FallbackWhenUsingHttp2?: boolean;
|
||||
useFakeLocalhost?: boolean;
|
||||
}): Promise<string>,
|
||||
};
|
||||
@ -42,7 +43,7 @@ const test = base.extend<TestOptions>({
|
||||
],
|
||||
requestCert: true,
|
||||
rejectUnauthorized: false,
|
||||
allowHTTP1: true,
|
||||
allowHTTP1: options?.enableHTTP1FallbackWhenUsingHttp2,
|
||||
}, (req: (http2.Http2ServerRequest | http.IncomingMessage), res: http2.Http2ServerResponse | http.ServerResponse) => {
|
||||
const tlsSocket = req.socket as import('tls').TLSSocket;
|
||||
const parts: { key: string, value: any }[] = [];
|
||||
@ -279,7 +280,8 @@ test.describe('browser', () => {
|
||||
|
||||
test('support http2', async ({ browser, startCCServer, asset, browserName }) => {
|
||||
test.skip(browserName === 'webkit' && process.platform === 'darwin', 'WebKit on macOS doesn\n proxy localhost');
|
||||
const serverURL = await startCCServer({ http2: true });
|
||||
const enableHTTP1FallbackWhenUsingHttp2 = browserName === 'webkit' && process.platform === 'linux';
|
||||
const serverURL = await startCCServer({ http2: true, enableHTTP1FallbackWhenUsingHttp2 });
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
origin: new URL(serverURL).origin,
|
||||
@ -289,7 +291,7 @@ test.describe('browser', () => {
|
||||
});
|
||||
// TODO: We should investigate why http2 is not supported in WebKit on Linux.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=276990
|
||||
const expectedProtocol = browserName === 'webkit' && process.platform === 'linux' ? 'http/1.1' : 'h2';
|
||||
const expectedProtocol = enableHTTP1FallbackWhenUsingHttp2 ? 'http/1.1' : 'h2';
|
||||
{
|
||||
await page.goto(serverURL.replace('localhost', 'local.playwright'));
|
||||
await expect(page.getByTestId('message')).toHaveText('Sorry, but you need to provide a client certificate to continue.');
|
||||
@ -306,7 +308,7 @@ test.describe('browser', () => {
|
||||
|
||||
test('support http2 if the browser only supports http1.1', async ({ browserType, browserName, startCCServer, asset }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
const serverURL = await startCCServer({ http2: true });
|
||||
const serverURL = await startCCServer({ http2: true, enableHTTP1FallbackWhenUsingHttp2: true });
|
||||
const browser = await browserType.launch({ args: ['--disable-http2'] });
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
@ -328,6 +330,25 @@ test.describe('browser', () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('should return target connection errors when using http2', async ({ browser, startCCServer, asset, browserName }) => {
|
||||
test.skip(browserName === 'webkit' && process.platform === 'darwin', 'WebKit on macOS doesn\n proxy localhost');
|
||||
test.fixme(browserName === 'webkit' && process.platform === 'linux', 'WebKit on Linux does not support http2 https://bugs.webkit.org/show_bug.cgi?id=276990');
|
||||
test.skip(+process.versions.node.split('.')[0] < 20, 'http2.performServerHandshake is not supported in older Node.js versions');
|
||||
|
||||
process.env.PWTEST_UNSUPPORTED_CUSTOM_CA = asset('empty.html');
|
||||
const serverURL = await startCCServer({ http2: true });
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
origin: 'https://just-there-that-the-client-certificates-proxy-server-is-getting-launched.com',
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
});
|
||||
await page.goto(serverURL);
|
||||
await expect(page.getByText('Playwright client-certificate error: self-signed certificate')).toBeVisible();
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test.describe('persistentContext', () => {
|
||||
test('validate input', async ({ launchPersistent }) => {
|
||||
test.slow();
|
||||
|
Loading…
Reference in New Issue
Block a user