From 6cb1e03713ad80245d22780b07c98cc5631fb2cf Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 28 Jul 2020 15:33:38 -0700 Subject: [PATCH] feat(rpc): disallow deps into rpc client from outside (#3199) For this, common converters are extracted from rpc serializers. --- package.json | 2 +- src/converters.ts | 123 +++++++++++++++++++++ src/dom.ts | 2 +- src/network.ts | 2 +- src/rpc/client/browser.ts | 2 +- src/rpc/client/browserContext.ts | 2 +- src/rpc/client/browserType.ts | 2 +- src/rpc/client/electron.ts | 2 +- src/rpc/client/elementHandle.ts | 2 +- src/rpc/client/network.ts | 2 +- src/rpc/client/page.ts | 3 +- src/rpc/serializers.ts | 107 +----------------- src/rpc/server/browserContextDispatcher.ts | 2 +- src/rpc/server/browserDispatcher.ts | 2 +- src/rpc/server/browserTypeDispatcher.ts | 2 +- src/rpc/server/electronDispatcher.ts | 2 +- src/rpc/server/networkDispatchers.ts | 2 +- src/rpc/server/pageDispatcher.ts | 3 +- src/webkit/wkInterceptableRequest.ts | 2 +- utils/check_deps.js | 6 +- 20 files changed, 148 insertions(+), 124 deletions(-) create mode 100644 src/converters.ts diff --git a/package.json b/package.json index f4b3eda484..e738d757d4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/converters.ts b/src/converters.ts new file mode 100644 index 0000000000..00fdbde9a1 --- /dev/null +++ b/src/converters.ts @@ -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 { + 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 { + 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; +} diff --git a/src/dom.ts b/src/dom.ts index e79261ec84..c3aa18701e 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -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; diff --git a/src/network.ts b/src/network.ts index 580c68fc70..a60b4506db 100644 --- a/src/network.ts +++ b/src/network.ts @@ -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)); diff --git a/src/rpc/client/browser.ts b/src/rpc/client/browser.ts index 942ed2b12d..1844c75d11 100644 --- a/src/rpc/client/browser.ts +++ b/src/rpc/client/browser.ts @@ -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 { readonly _contexts = new Set(); diff --git a/src/rpc/client/browserContext.ts b/src/rpc/client/browserContext.ts index 13a7b85a3d..e87e6d729e 100644 --- a/src/rpc/client/browserContext.ts +++ b/src/rpc/client/browserContext.ts @@ -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 { _pages = new Set(); diff --git a/src/rpc/client/browserType.ts b/src/rpc/client/browserType.ts index 465423a7d6..1b886d5f0c 100644 --- a/src/rpc/client/browserType.ts +++ b/src/rpc/client/browserType.ts @@ -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'; diff --git a/src/rpc/client/electron.ts b/src/rpc/client/electron.ts index 458e2280e8..2c06873b56 100644 --- a/src/rpc/client/electron.ts +++ b/src/rpc/client/electron.ts @@ -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 { static from(electron: ElectronChannel): Electron { diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index 1930c8c7f0..1bc13d8cca 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -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 extends JSHandle { readonly _elementChannel: ElementHandleChannel; diff --git a/src/rpc/client/network.ts b/src/rpc/client/network.ts index 9159f1456d..aa144d40da 100644 --- a/src/rpc/client/network.ts +++ b/src/rpc/client/network.ts @@ -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, diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index 4433f690ab..f09750486e 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -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'; diff --git a/src/rpc/serializers.ts b/src/rpc/serializers.ts index f255c54ddc..d5b3c6c7a1 100644 --- a/src/rpc/serializers.ts +++ b/src/rpc/serializers.ts @@ -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 { - 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 { - 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, diff --git a/src/rpc/server/browserContextDispatcher.ts b/src/rpc/server/browserContextDispatcher.ts index 0c5448fe31..851c15bc77 100644 --- a/src/rpc/server/browserContextDispatcher.ts +++ b/src/rpc/server/browserContextDispatcher.ts @@ -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 implements BrowserContextChannel { private _context: BrowserContextBase; diff --git a/src/rpc/server/browserDispatcher.ts b/src/rpc/server/browserDispatcher.ts index de7d0cf3b0..9a5857a68e 100644 --- a/src/rpc/server/browserDispatcher.ts +++ b/src/rpc/server/browserDispatcher.ts @@ -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 implements BrowserChannel { constructor(scope: DispatcherScope, browser: BrowserBase) { diff --git a/src/rpc/server/browserTypeDispatcher.ts b/src/rpc/server/browserTypeDispatcher.ts index 771d0b0196..87ee56c7f9 100644 --- a/src/rpc/server/browserTypeDispatcher.ts +++ b/src/rpc/server/browserTypeDispatcher.ts @@ -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 implements BrowserTypeChannel { diff --git a/src/rpc/server/electronDispatcher.ts b/src/rpc/server/electronDispatcher.ts index 91a8ab5b89..b6ff0a6fdd 100644 --- a/src/rpc/server/electronDispatcher.ts +++ b/src/rpc/server/electronDispatcher.ts @@ -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 implements ElectronChannel { constructor(scope: DispatcherScope, electron: Electron) { diff --git a/src/rpc/server/networkDispatchers.ts b/src/rpc/server/networkDispatchers.ts index 0f466c536a..2be678be13 100644 --- a/src/rpc/server/networkDispatchers.ts +++ b/src/rpc/server/networkDispatchers.ts @@ -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 implements RequestChannel { diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index cb282ad157..ac50374bda 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -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'; diff --git a/src/webkit/wkInterceptableRequest.ts b/src/webkit/wkInterceptableRequest.ts index f3d19ac1a1..1491ff1edb 100644 --- a/src/webkit/wkInterceptableRequest.ts +++ b/src/webkit/wkInterceptableRequest.ts @@ -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', diff --git a/utils/check_deps.js b/utils/check_deps.js index 09536896e8..269ffd5c5f 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -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; } }