feat(rpc): disallow deps into rpc client from outside (#3199)

For this, common converters are extracted from rpc serializers.
This commit is contained in:
Dmitry Gozman 2020-07-28 15:33:38 -07:00 committed by GitHub
parent 3e023f6c3d
commit 6cb1e03713
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 148 additions and 124 deletions

View file

@ -18,7 +18,7 @@
"tsc-installer": "tsc -p ./src/install/tsconfig.json",
"doc": "node utils/doclint/cli.js",
"test-infra": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js && node utils/testrunner/test/test.js",
"lint": "npm run eslint && npm run tsc && npm run doc && npm run test-types && npm run test-infra",
"lint": "npm run eslint && npm run tsc && npm run doc && npm run check-deps && npm run test-types && npm run test-infra",
"debug-test": "node --inspect-brk test/test.js",
"clean": "rimraf lib && rimraf types",
"prepare": "node install-from-github.js",

123
src/converters.ts Normal file
View file

@ -0,0 +1,123 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import * as util from 'util';
import * as types from './types';
import { helper, assert } from './helper';
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
let ff: string[] | types.FilePayload[];
if (!Array.isArray(files))
ff = [ files ] as string[] | types.FilePayload[];
else
ff = files;
const filePayloads: types.FilePayload[] = [];
for (const item of ff) {
if (typeof item === 'string') {
const file: types.FilePayload = {
name: path.basename(item),
mimeType: mime.getType(item) || 'application/octet-stream',
buffer: await util.promisify(fs.readFile)(item)
};
filePayloads.push(file);
} else {
filePayloads.push(item);
}
}
return filePayloads;
}
export async function normalizeFulfillParameters(params: types.FulfillResponse & { path?: string }): Promise<types.NormalizedFulfillResponse> {
let body = '';
let isBase64 = false;
let length = 0;
if (params.path) {
const buffer = await util.promisify(fs.readFile)(params.path);
body = buffer.toString('base64');
isBase64 = true;
length = buffer.length;
} else if (helper.isString(params.body)) {
body = params.body;
isBase64 = false;
length = Buffer.byteLength(body);
} else if (params.body) {
body = params.body.toString('base64');
isBase64 = true;
length = params.body.length;
}
const headers: types.Headers = {};
for (const header of Object.keys(params.headers || {}))
headers[header.toLowerCase()] = String(params.headers![header]);
if (params.contentType)
headers['content-type'] = String(params.contentType);
else if (params.path)
headers['content-type'] = mime.getType(params.path) || 'application/octet-stream';
if (length && !('content-length' in headers))
headers['content-length'] = String(length);
return {
status: params.status || 200,
headers: headersObjectToArray(headers),
body,
isBase64
};
}
export function normalizeContinueOverrides(overrides: types.ContinueOverrides): types.NormalizedContinueOverrides {
return {
method: overrides.method,
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
postData: helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData,
};
}
export function headersObjectToArray(headers: types.Headers): types.HeadersArray {
const result: types.HeadersArray = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined)) {
const value = headers[name];
assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
result.push({ name, value });
}
}
return result;
}
export function headersArrayToObject(headers: types.HeadersArray): types.Headers {
const result: types.Headers = {};
for (const { name, value } of headers)
result[name] = value;
return result;
}
export function envObjectToArray(env: types.Env): types.EnvArray {
const result: types.EnvArray = [];
for (const name in env) {
if (!Object.is(env[name], undefined))
result.push({ name, value: String(env[name]) });
}
return result;
}
export function envArrayToObject(env: types.EnvArray): types.Env {
const result: types.Env = {};
for (const { name, value } of env)
result[name] = value;
return result;
}

View file

@ -26,7 +26,7 @@ import * as types from './types';
import { Progress } from './progress';
import DebugScript from './debug/injected/debugScript';
import { FatalDOMError, RetargetableDOMError } from './common/domErrors';
import { normalizeFilePayloads } from './rpc/serializers';
import { normalizeFilePayloads } from './converters';
export class FrameExecutionContext extends js.ExecutionContext {
readonly frame: frames.Frame;

View file

@ -18,7 +18,7 @@ import * as frames from './frames';
import * as types from './types';
import { assert, helper } from './helper';
import { URLSearchParams } from 'url';
import { normalizeFulfillParameters, normalizeContinueOverrides } from './rpc/serializers';
import { normalizeFulfillParameters, normalizeContinueOverrides } from './converters';
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
const parsedURLs = urls.map(s => new URL(s));

View file

@ -22,7 +22,7 @@ import { ChannelOwner } from './channelOwner';
import { Events } from '../../events';
import { LoggerSink } from '../../loggerSink';
import { BrowserType } from './browserType';
import { headersObjectToArray } from '../serializers';
import { headersObjectToArray } from '../../converters';
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
readonly _contexts = new Set<BrowserContext>();

View file

@ -26,7 +26,7 @@ import { Browser } from './browser';
import { Events } from '../../events';
import { TimeoutSettings } from '../../timeoutSettings';
import { Waiter } from './waiter';
import { headersObjectToArray } from '../serializers';
import { headersObjectToArray } from '../../converters';
export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserContextInitializer> {
_pages = new Set<Page>();

View file

@ -21,7 +21,7 @@ import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { BrowserServer } from './browserServer';
import { LoggerSink } from '../../loggerSink';
import { headersObjectToArray, envObjectToArray } from '../serializers';
import { headersObjectToArray, envObjectToArray } from '../../converters';
import { serializeArgument } from './jsHandle';
import { assert } from '../../helper';

View file

@ -25,7 +25,7 @@ import { TimeoutSettings } from '../../timeoutSettings';
import { Waiter } from './waiter';
import { Events } from '../../events';
import { LoggerSink } from '../../loggerSink';
import { envObjectToArray } from '../serializers';
import { envObjectToArray } from '../../converters';
export class Electron extends ChannelOwner<ElectronChannel, ElectronInitializer> {
static from(electron: ElectronChannel): Electron {

View file

@ -20,7 +20,7 @@ import { Frame } from './frame';
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
import { ChannelOwner } from './channelOwner';
import { helper, assert } from '../../helper';
import { normalizeFilePayloads } from '../serializers';
import { normalizeFilePayloads } from '../../converters';
export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
readonly _elementChannel: ElementHandleChannel;

View file

@ -19,7 +19,7 @@ import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../serializers';
import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../../converters';
export type NetworkCookie = {
name: string,

View file

@ -20,7 +20,8 @@ import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PagePdfParams } from '../channels';
import { parseError, headersObjectToArray, serializeError } from '../serializers';
import { parseError, serializeError } from '../serializers';
import { headersObjectToArray } from '../../converters';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';

View file

@ -14,13 +14,9 @@
* limitations under the License.
*/
import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import * as util from 'util';
import { TimeoutError } from '../errors';
import * as types from '../types';
import { helper, assert } from '../helper';
import { helper } from '../helper';
import { SerializedError, AXNode, SerializedValue } from './channels';
export function serializeError(e: any): SerializedError {
@ -45,107 +41,6 @@ export function parseError(error: SerializedError): Error {
return e;
}
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
let ff: string[] | types.FilePayload[];
if (!Array.isArray(files))
ff = [ files ] as string[] | types.FilePayload[];
else
ff = files;
const filePayloads: types.FilePayload[] = [];
for (const item of ff) {
if (typeof item === 'string') {
const file: types.FilePayload = {
name: path.basename(item),
mimeType: mime.getType(item) || 'application/octet-stream',
buffer: await util.promisify(fs.readFile)(item)
};
filePayloads.push(file);
} else {
filePayloads.push(item);
}
}
return filePayloads;
}
export async function normalizeFulfillParameters(params: types.FulfillResponse & { path?: string }): Promise<types.NormalizedFulfillResponse> {
let body = '';
let isBase64 = false;
let length = 0;
if (params.path) {
const buffer = await util.promisify(fs.readFile)(params.path);
body = buffer.toString('base64');
isBase64 = true;
length = buffer.length;
} else if (helper.isString(params.body)) {
body = params.body;
isBase64 = false;
length = Buffer.byteLength(body);
} else if (params.body) {
body = params.body.toString('base64');
isBase64 = true;
length = params.body.length;
}
const headers: types.Headers = {};
for (const header of Object.keys(params.headers || {}))
headers[header.toLowerCase()] = String(params.headers![header]);
if (params.contentType)
headers['content-type'] = String(params.contentType);
else if (params.path)
headers['content-type'] = mime.getType(params.path) || 'application/octet-stream';
if (length && !('content-length' in headers))
headers['content-length'] = String(length);
return {
status: params.status || 200,
headers: headersObjectToArray(headers),
body,
isBase64
};
}
export function normalizeContinueOverrides(overrides: types.ContinueOverrides): types.NormalizedContinueOverrides {
return {
method: overrides.method,
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
postData: helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData,
};
}
export function headersObjectToArray(headers: types.Headers): types.HeadersArray {
const result: types.HeadersArray = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined)) {
const value = headers[name];
assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
result.push({ name, value });
}
}
return result;
}
export function headersArrayToObject(headers: types.HeadersArray): types.Headers {
const result: types.Headers = {};
for (const { name, value } of headers)
result[name] = value;
return result;
}
export function envObjectToArray(env: types.Env): types.EnvArray {
const result: types.EnvArray = [];
for (const name in env) {
if (!Object.is(env[name], undefined))
result.push({ name, value: String(env[name]) });
}
return result;
}
export function envArrayToObject(env: types.EnvArray): types.Env {
const result: types.Env = {};
for (const { name, value } of env)
result[name] = value;
return result;
}
export function axNodeToProtocol(axNode: types.SerializedAXNode): AXNode {
const result: AXNode = {
...axNode,

View file

@ -24,7 +24,7 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../../chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Events as ChromiumEvents } from '../../chromium/events';
import { headersArrayToObject } from '../serializers';
import { headersArrayToObject } from '../../converters';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
private _context: BrowserContextBase;

View file

@ -23,7 +23,7 @@ import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Dispatcher, DispatcherScope } from './dispatcher';
import { CRBrowser } from '../../chromium/crBrowser';
import { PageDispatcher } from './pageDispatcher';
import { headersArrayToObject } from '../serializers';
import { headersArrayToObject } from '../../converters';
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
constructor(scope: DispatcherScope, browser: BrowserBase) {

View file

@ -23,7 +23,7 @@ import { Dispatcher, DispatcherScope } from './dispatcher';
import { BrowserContextBase } from '../../browserContext';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserServerDispatcher } from './browserServerDispatcher';
import { headersArrayToObject, envArrayToObject } from '../serializers';
import { headersArrayToObject, envArrayToObject } from '../../converters';
import { parseValue } from './jsHandleDispatcher';
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {

View file

@ -22,7 +22,7 @@ import { BrowserContextBase } from '../../browserContext';
import { PageDispatcher } from './pageDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { createHandle } from './elementHandlerDispatcher';
import { envArrayToObject } from '../serializers';
import { envArrayToObject } from '../../converters';
export class ElectronDispatcher extends Dispatcher<Electron, ElectronInitializer> implements ElectronChannel {
constructor(scope: DispatcherScope, electron: Electron) {

View file

@ -18,7 +18,7 @@ import { Request, Response, Route } from '../../network';
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { headersObjectToArray, headersArrayToObject } from '../serializers';
import { headersObjectToArray, headersArrayToObject } from '../../converters';
import * as types from '../../types';
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {

View file

@ -22,7 +22,8 @@ import { Page, Worker } from '../../page';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError, PageAccessibilitySnapshotResult, SerializedValue, PageEmulateMediaParams } from '../channels';
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
import { parseError, serializeError, headersArrayToObject, axNodeToProtocol } from '../serializers';
import { parseError, serializeError, axNodeToProtocol } from '../serializers';
import { headersArrayToObject } from '../../converters';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { DialogDispatcher } from './dialogDispatcher';
import { DownloadDispatcher } from './downloadDispatcher';

View file

@ -21,7 +21,7 @@ import * as network from '../network';
import * as types from '../types';
import { Protocol } from './protocol';
import { WKSession } from './wkConnection';
import { headersArrayToObject } from '../rpc/serializers';
import { headersArrayToObject } from '../converters';
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
'aborted': 'Cancellation',

View file

@ -53,8 +53,12 @@ async function checkDeps() {
const rpc = path.join('src', 'rpc');
if (!from.includes(rpc) && to.includes(rpc))
return false;
if (from.includes(rpc) && !to.includes(rpc))
const rpcClient = path.join('src', 'rpc', 'client');
const rpcServer = path.join('src', 'rpc', 'server');
if (from.includes(rpcClient) && to.includes(rpcServer))
return false;
// if (from.includes(rpcClient) && !to.includes(rpc))
// return false;
return true;
}
}