From 8b7e6d0bf1a9eea826beb47b2b52eecfee0c3d88 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Tue, 8 Jan 2019 23:27:56 +0100 Subject: [PATCH] feat: add additional remote APIs filtering --- docs/api/app.md | 46 ++++++++++++++++++++ docs/api/web-contents.md | 42 ++++++++++++++++++ lib/browser/api/web-contents.js | 21 ++++++--- lib/browser/rpc-server.js | 75 ++++++++++++++++++++++++++------- spec/api-app-spec.js | 64 ++++++++++++++++++++++------ spec/api-remote-spec.js | 46 ++++++++++++++++++-- spec/static/main.js | 42 ++++++++++++------ spec/webview-spec.js | 18 ++++++++ 8 files changed, 300 insertions(+), 54 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 4ef5f8404135f..836490852a178 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -422,6 +422,52 @@ Emitted when `remote.getGlobal()` is called in the renderer process of `webConte Calling `event.preventDefault()` will prevent the global from being returned. Custom value can be returned by setting `event.returnValue`. +### Event: 'remote-get-builtin' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `moduleName` String + +Emitted when `remote.getBuiltin()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the module from being returned. +Custom value can be returned by setting `event.returnValue`. + +### Event: 'remote-get-current-window' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) + +Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + +### Event: 'remote-get-current-web-contents' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) + +Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + +### Event: 'remote-get-guest-web-contents' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `guestWebContents` [WebContents](web-contents.md) + +Emitted when `.getWebContents()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + ## Methods The `app` object has the following methods: diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 9373f82874e89..c790c3640a485 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -685,6 +685,48 @@ Emitted when `remote.getGlobal()` is called in the renderer process. Calling `event.preventDefault()` will prevent the global from being returned. Custom value can be returned by setting `event.returnValue`. +#### Event: 'remote-get-builtin' + +Returns: + +* `event` Event +* `moduleName` String + +Emitted when `remote.getBuiltin()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the module from being returned. +Custom value can be returned by setting `event.returnValue`. + +#### Event: 'remote-get-current-window' + +Returns: + +* `event` Event + +Emitted when `remote.getCurrentWindow()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + +#### Event: 'remote-get-current-web-contents' + +Returns: + +* `event` Event + +Emitted when `remote.getCurrentWebContents()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + +#### Event: 'remote-get-guest-web-contents' + +Returns: + +* `event` Event +* `guestWebContents` [WebContents](web-contents.md) + +Emitted when `.getWebContents()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the object from being returned. +Custom value can be returned by setting `event.returnValue`. + ### Instance Methods #### `contents.loadURL(url[, options])` diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 4dd30f298b012..685d0eb59fce0 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -369,13 +369,20 @@ WebContents.prototype._init = function () { }) }) - this.on('remote-require', (event, ...args) => { - app.emit('remote-require', event, this, ...args) - }) - - this.on('remote-get-global', (event, ...args) => { - app.emit('remote-get-global', event, this, ...args) - }) + const forwardedEvents = [ + 'remote-require', + 'remote-get-global', + 'remote-get-builtin', + 'remote-get-current-window', + 'remote-get-current-web-contents', + 'remote-get-guest-web-contents' + ] + + for (const eventName of forwardedEvents) { + this.on(eventName, (event, ...args) => { + app.emit(eventName, event, this, ...args) + }) + } deprecate.event(this, 'did-get-response-details', '-did-get-response-details') deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request') diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index dd72237de7749..0e31d223f3f53 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -296,42 +296,75 @@ handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, modu const customEvent = eventBinding.createWithSender(event.sender) event.sender.emit('remote-require', customEvent, moduleName) - if (customEvent.defaultPrevented) { - if (typeof customEvent.returnValue === 'undefined') { - throw new Error(`Invalid module: ${moduleName}`) + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error(`Blocked remote.require('${moduleName}')`) + } else { + customEvent.returnValue = process.mainModule.require(moduleName) } - } else { - customEvent.returnValue = process.mainModule.require(moduleName) } return valueToMeta(event.sender, contextId, customEvent.returnValue) }) -handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) { - return valueToMeta(event.sender, contextId, electron[module]) +handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) { + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-get-builtin', customEvent, moduleName) + + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error(`Blocked remote.getBuiltin('${moduleName}')`) + } else { + customEvent.returnValue = electron[moduleName] + } + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) { const customEvent = eventBinding.createWithSender(event.sender) event.sender.emit('remote-get-global', customEvent, globalName) - if (customEvent.defaultPrevented) { - if (typeof customEvent.returnValue === 'undefined') { - throw new Error(`Invalid global: ${globalName}`) + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error(`Blocked remote.getGlobal('${globalName}')`) + } else { + customEvent.returnValue = global[globalName] } - } else { - customEvent.returnValue = global[globalName] } return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) { - return valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow()) + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-get-current-window', customEvent) + + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error('Blocked remote.getCurrentWindow()') + } else { + customEvent.returnValue = event.sender.getOwnerBrowserWindow() + } + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) { - return valueToMeta(event.sender, contextId, event.sender) + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-get-current-web-contents', customEvent) + + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error('Blocked remote.getCurrentWebContents()') + } else { + customEvent.returnValue = event.sender + } + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { @@ -411,7 +444,19 @@ handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => { handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) { const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender) - return valueToMeta(event.sender, contextId, guest) + + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-get-guest-web-contents', customEvent, guest) + + if (customEvent.returnValue === undefined) { + if (customEvent.defaultPrevented) { + throw new Error(`Blocked remote.getGuestForWebContents()`) + } else { + customEvent.returnValue = guest + } + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) // Implements window.close() diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index ae9f8133a7ccd..e147943495912 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -359,26 +359,62 @@ describe('app module', () => { w = new BrowserWindow({ show: false }) }) - it('should emit remote-require event when remote.require() is invoked', (done) => { - app.once('remote-require', (event, webContents, moduleName) => { - expect(webContents).to.equal(w.webContents) - expect(moduleName).to.equal('test') - done() - }) + it('should emit remote-require event when remote.require() is invoked', async () => { w = new BrowserWindow({ show: false }) - w.loadURL('about:blank') + await w.loadURL('about:blank') + + const promise = emittedOnce(app, 'remote-require') w.webContents.executeJavaScript(`require('electron').remote.require('test')`) + + const [, webContents, moduleName] = await promise + expect(webContents).to.equal(w.webContents) + expect(moduleName).to.equal('test') }) - it('should emit remote-get-global event when remote.getGlobal() is invoked', (done) => { - app.once('remote-get-global', (event, webContents, globalName) => { - expect(webContents).to.equal(w.webContents) - expect(globalName).to.equal('test') - done() - }) + it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => { w = new BrowserWindow({ show: false }) - w.loadURL('about:blank') + await w.loadURL('about:blank') + + const promise = emittedOnce(app, 'remote-get-global') w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`) + + const [, webContents, globalName] = await promise + expect(webContents).to.equal(w.webContents) + expect(globalName).to.equal('test') + }) + + it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => { + w = new BrowserWindow({ show: false }) + await w.loadURL('about:blank') + + const promise = emittedOnce(app, 'remote-get-builtin') + w.webContents.executeJavaScript(`require('electron').remote.app`) + + const [, webContents, moduleName] = await promise + expect(webContents).to.equal(w.webContents) + expect(moduleName).to.equal('app') + }) + + it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => { + w = new BrowserWindow({ show: false }) + await w.loadURL('about:blank') + + const promise = emittedOnce(app, 'remote-get-current-window') + w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`) + + const [, webContents] = await promise + expect(webContents).to.equal(w.webContents) + }) + + it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => { + w = new BrowserWindow({ show: false }) + await w.loadURL('about:blank') + + const promise = emittedOnce(app, 'remote-get-current-web-contents') + w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`) + + const [, webContents] = await promise + expect(webContents).to.equal(w.webContents) }) }) diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index 1d0bf1a6ca845..fa94f1a9bb1ef 100644 --- a/spec/api-remote-spec.js +++ b/spec/api-remote-spec.js @@ -28,7 +28,7 @@ describe('remote module', () => { afterEach(() => closeWindow(w).then(() => { w = null })) - describe('remote.getGlobal', () => { + describe('remote.getGlobal filtering', () => { it('can return custom values', () => { ipcRenderer.send('handle-next-remote-get-global', { test: 'Hello World!' }) expect(remote.getGlobal('test')).to.be.equal('Hello World!') @@ -36,11 +36,23 @@ describe('remote module', () => { it('throws when no returnValue set', () => { ipcRenderer.send('handle-next-remote-get-global') - expect(() => remote.getGlobal('process')).to.throw('Invalid global: process') + expect(() => remote.getGlobal('test')).to.throw(`Blocked remote.getGlobal('test')`) }) }) - describe('remote.require', () => { + describe('remote.getBuiltin filtering', () => { + it('can return custom values', () => { + ipcRenderer.send('handle-next-remote-get-builtin', { test: 'Hello World!' }) + expect(remote.getBuiltin('test')).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', () => { + ipcRenderer.send('handle-next-remote-get-builtin') + expect(() => remote.getBuiltin('test')).to.throw(`Blocked remote.getBuiltin('test')`) + }) + }) + + describe('remote.require filtering', () => { it('can return custom values', () => { ipcRenderer.send('handle-next-remote-require', { test: 'Hello World!' }) expect(remote.require('test')).to.be.equal('Hello World!') @@ -48,9 +60,11 @@ describe('remote module', () => { it('throws when no returnValue set', () => { ipcRenderer.send('handle-next-remote-require') - expect(() => remote.require('electron')).to.throw('Invalid module: electron') + expect(() => remote.require('test')).to.throw(`Blocked remote.require('test')`) }) + }) + describe('remote.require', () => { it('should returns same object for the same module', () => { const dialog1 = remote.require('electron') const dialog2 = remote.require('electron') @@ -450,6 +464,30 @@ describe('remote module', () => { }) }) + describe('remote.getCurrentWindow filtering', () => { + it('can return custom value', () => { + ipcRenderer.send('handle-next-remote-get-current-window', 'Hello World!') + expect(remote.getCurrentWindow()).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', () => { + ipcRenderer.send('handle-next-remote-get-current-window') + expect(() => remote.getCurrentWindow()).to.throw('Blocked remote.getCurrentWindow()') + }) + }) + + describe('remote.getCurrentWebContents filtering', () => { + it('can return custom value', () => { + ipcRenderer.send('handle-next-remote-get-current-web-contents', 'Hello World!') + expect(remote.getCurrentWebContents()).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', () => { + ipcRenderer.send('handle-next-remote-get-current-web-contents') + expect(() => remote.getCurrentWebContents()).to.throw('Blocked remote.getCurrentWebContents()') + }) + }) + describe('remote class', () => { const cl = remote.require(path.join(fixtures, 'module', 'class.js')) const base = cl.base diff --git a/spec/static/main.js b/spec/static/main.js index 415d96b0c57f7..49132a4913c28 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -242,23 +242,37 @@ app.on('ready', function () { }) }) -ipcMain.on('handle-next-remote-require', function (event, modulesMap = {}) { - event.sender.once('remote-require', (event, moduleName) => { - event.preventDefault() - if (modulesMap.hasOwnProperty(moduleName)) { - event.returnValue = modulesMap[moduleName] - } +for (const eventName of [ + 'remote-require', + 'remote-get-global', + 'remote-get-builtin' +]) { + ipcMain.on(`handle-next-${eventName}`, function (event, valuesMap = {}) { + event.sender.once(eventName, (event, name) => { + if (valuesMap.hasOwnProperty(name)) { + event.returnValue = valuesMap[name] + } else { + event.preventDefault() + } + }) }) -}) +} -ipcMain.on('handle-next-remote-get-global', function (event, globalsMap = {}) { - event.sender.once('remote-get-global', (event, globalName) => { - event.preventDefault() - if (globalsMap.hasOwnProperty(globalName)) { - event.returnValue = globalsMap[globalName] - } +for (const eventName of [ + 'remote-get-current-window', + 'remote-get-current-web-contents', + 'remote-get-guest-web-contents' +]) { + ipcMain.on(`handle-next-${eventName}`, function (event, returnValue) { + event.sender.once(eventName, (event) => { + if (returnValue) { + event.returnValue = returnValue + } else { + event.preventDefault() + } + }) }) -}) +} ipcMain.on('set-client-certificate-option', function (event, skip) { app.once('select-client-certificate', function (event, webContents, url, list, callback) { diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 6357d4f97fb7c..67e2464f9eea6 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1197,6 +1197,24 @@ describe(' tag', function () { }) }) + describe('.getWebContents filtering', () => { + it('can return custom value', async () => { + const src = 'about:blank' + await loadWebView(webview, { src }) + + ipcRenderer.send('handle-next-remote-get-guest-web-contents', 'Hello World!') + expect(webview.getWebContents()).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', async () => { + const src = 'about:blank' + await loadWebView(webview, { src }) + + ipcRenderer.send('handle-next-remote-get-guest-web-contents') + expect(() => webview.getWebContents()).to.throw('Blocked remote.getGuestForWebContents()') + }) + }) + // FIXME(deepak1556): Ch69 follow up. xdescribe('document.visibilityState/hidden', () => { afterEach(() => {