fix(channels): only send protocol methods to connection (#4493)

When using 'domain' module, it calls various EventEmitter methods
like 'listenerCount' that we do not expect. To avoid this problem
in the future, we validate the method name before sending it over
the protocol connection.
This commit is contained in:
Dmitry Gozman 2020-11-26 07:33:09 -08:00 committed by GitHub
parent 7d90f5ef25
commit 884edbb3ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 38 deletions

View file

@ -20,6 +20,7 @@ import type { Connection } from './connection';
import type { Logger } from './types';
import { debugLogger } from '../utils/debugLogger';
import { rewriteErrorMessage } from '../utils/stackTrace';
import { createScheme, Validator, ValidationError } from '../protocol/validator';
export abstract class ChannelOwner<T extends channels.Channel = channels.Channel, Initializer = {}> extends EventEmitter {
private _connection: Connection;
@ -48,23 +49,14 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
const base = new EventEmitter();
this._channel = new Proxy(base, {
get: (obj: any, prop) => {
if (String(prop).startsWith('_'))
return obj[prop];
if (prop === 'then')
return obj.then;
if (prop === 'emit')
return obj.emit;
if (prop === 'on')
return obj.on;
if (prop === 'once')
return obj.once;
if (prop === 'addEventListener')
return obj.addListener;
if (prop === 'removeEventListener')
return obj.removeListener;
if (prop === 'domain') // https://github.com/microsoft/playwright/issues/3848
return obj.domain;
return (params: any) => this._connection.sendMessageToServer(this._type, guid, String(prop), params);
if (prop === 'debugScopeState')
return (params: any) => this._connection.sendMessageToServer(guid, prop, params);
if (typeof prop === 'string') {
const validator = scheme[paramsName(type, prop)];
if (validator)
return (params: any) => this._connection.sendMessageToServer(guid, prop, validator(params, ''));
}
return obj[prop];
},
});
(this._channel as any)._object = this;
@ -121,3 +113,17 @@ function logApiCall(logger: Logger | undefined, message: string) {
logger.log('api', 'info', message, [], { color: 'cyan' });
debugLogger.log('api', message);
}
function paramsName(type: string, method: string) {
return type + method[0].toUpperCase() + method.substring(1) + 'Params';
}
const tChannel = (name: string): Validator => {
return (arg: any, path: string) => {
if (arg._object instanceof ChannelOwner && (name === '*' || arg._object._type === name))
return { guid: arg._object._guid };
throw new ValidationError(`${path}: expected ${name}`);
};
};
const scheme = createScheme(tChannel);

View file

@ -35,7 +35,6 @@ import * as channels from '../protocol/channels';
import { ChromiumBrowser } from './chromiumBrowser';
import { ChromiumBrowserContext } from './chromiumBrowserContext';
import { Stream } from './stream';
import { createScheme, Validator, ValidationError } from '../protocol/validator';
import { WebKitBrowser } from './webkitBrowser';
import { FirefoxBrowser } from './firefoxBrowser';
import { debugLogger } from '../utils/debugLogger';
@ -70,13 +69,12 @@ export class Connection {
return this._objects.get(guid)!;
}
async sendMessageToServer(type: string, guid: string, method: string, params: any): Promise<any> {
async sendMessageToServer(guid: string, method: string, params: any): Promise<any> {
const stackObject: any = {};
Error.captureStackTrace(stackObject);
const stack = stackObject.stack.startsWith('Error') ? stackObject.stack.substring(5) : stackObject.stack;
const id = ++this._lastId;
const validated = method === 'debugScopeState' ? params : validateParams(type, method, params);
const converted = { id, guid, method, params: validated };
const converted = { id, guid, method, params };
// Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel:command', converted);
this.onmessage({ ...converted, metadata: { stack } });
@ -243,20 +241,3 @@ export class Connection {
return result;
}
}
const tChannel = (name: string): Validator => {
return (arg: any, path: string) => {
if (arg._object instanceof ChannelOwner && (name === '*' || arg._object._type === name))
return { guid: arg._object._guid };
throw new ValidationError(`${path}: expected ${name}`);
};
};
const scheme = createScheme(tChannel);
function validateParams(type: string, method: string, params: any): any {
const name = type + method[0].toUpperCase() + method.substring(1) + 'Params';
if (!scheme[name])
throw new ValidationError(`Unknown scheme for ${type}.${method}`);
return scheme[name](params, '');
}