Browse Source

feat: add remote.require() / remote.getGlobal() filtering (#15562)

Milan Burda 6 years ago
parent
commit
f43920e436

+ 26 - 0
atom/browser/api/atom_api_event.cc

@@ -0,0 +1,26 @@
+// Copyright (c) 2018 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "atom/browser/api/event_emitter.h"
+#include "atom/common/node_includes.h"
+#include "native_mate/dictionary.h"
+
+namespace {
+
+v8::Local<v8::Object> CreateWithSender(v8::Isolate* isolate,
+                                       v8::Local<v8::Object> sender) {
+  return mate::internal::CreateJSEvent(isolate, sender, nullptr, nullptr);
+}
+
+void Initialize(v8::Local<v8::Object> exports,
+                v8::Local<v8::Value> unused,
+                v8::Local<v8::Context> context,
+                void* priv) {
+  mate::Dictionary dict(context->GetIsolate(), exports);
+  dict.SetMethod("createWithSender", &CreateWithSender);
+}
+
+}  // namespace
+
+NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_event, Initialize)

+ 1 - 0
atom/common/node_bindings.cc

@@ -38,6 +38,7 @@
   V(atom_browser_debugger)                   \
   V(atom_browser_dialog)                     \
   V(atom_browser_download_item)              \
+  V(atom_browser_event)                      \
   V(atom_browser_global_shortcut)            \
   V(atom_browser_in_app_purchase)            \
   V(atom_browser_menu)                       \

+ 24 - 0
docs/api/app.md

@@ -398,6 +398,30 @@ non-minimized.
 This event is guaranteed to be emitted after the `ready` event of `app`
 gets emitted.
 
+### Event: 'remote-require'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+* `moduleName` String
+
+Emitted when `remote.require()` 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-global'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+* `globalName` String
+
+Emitted when `remote.getGlobal()` is called in the renderer process of `webContents`.
+Calling `event.preventDefault()` will prevent the global from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
 ## Methods
 
 The `app` object has the following methods:

+ 22 - 0
docs/api/web-contents.md

@@ -663,6 +663,28 @@ Returns:
 Emitted when the associated window logs a console message. Will not be emitted
 for windows with *offscreen rendering* enabled.
 
+#### Event: 'remote-require'
+
+Returns:
+
+* `event` Event
+* `moduleName` String
+
+Emitted when `remote.require()` 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-global'
+
+Returns:
+
+* `event` Event
+* `globalName` String
+
+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`.
+
 ### Instance Methods
 
 #### `contents.loadURL(url[, options])`

+ 1 - 0
filenames.gni

@@ -133,6 +133,7 @@ filenames = {
     "atom/browser/api/atom_api_dialog.cc",
     "atom/browser/api/atom_api_download_item.cc",
     "atom/browser/api/atom_api_download_item.h",
+    "atom/browser/api/atom_api_event.cc",
     "atom/browser/api/atom_api_global_shortcut.cc",
     "atom/browser/api/atom_api_global_shortcut.h",
     "atom/browser/api/atom_api_in_app_purchase.cc",

+ 8 - 0
lib/browser/api/web-contents.js

@@ -336,6 +336,14 @@ 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)
+  })
+
   deprecate.event(this, 'did-get-response-details', '-did-get-response-details')
   deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request')
 

+ 27 - 4
lib/browser/rpc-server.js

@@ -7,6 +7,7 @@ const fs = require('fs')
 const os = require('os')
 const path = require('path')
 const v8Util = process.atomBinding('v8_util')
+const eventBinding = process.atomBinding('event')
 
 const { isPromise } = electron
 
@@ -290,16 +291,38 @@ handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, con
   removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
 })
 
-handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) {
-  return valueToMeta(event.sender, contextId, process.mainModule.require(module))
+handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
+  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}`)
+    }
+  } 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_GLOBAL', function (event, contextId, name) {
-  return valueToMeta(event.sender, contextId, global[name])
+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}`)
+    }
+  } else {
+    customEvent.returnValue = global[globalName]
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {

+ 22 - 0
spec/api-app-spec.js

@@ -358,6 +358,28 @@ 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()
+      })
+      w = new BrowserWindow({ show: false })
+      w.loadURL('about:blank')
+      w.webContents.executeJavaScript(`require('electron').remote.require('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()
+      })
+      w = new BrowserWindow({ show: false })
+      w.loadURL('about:blank')
+      w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`)
+    })
   })
 
   describe('app.setBadgeCount', () => {

+ 28 - 1
spec/api-remote-spec.js

@@ -1,12 +1,17 @@
 'use strict'
 
 const assert = require('assert')
+const chai = require('chai')
+const dirtyChai = require('dirty-chai')
 const path = require('path')
 const { closeWindow } = require('./window-helpers')
 const { resolveGetters } = require('./assert-helpers')
 
-const { remote } = require('electron')
+const { remote, ipcRenderer } = require('electron')
 const { ipcMain, BrowserWindow } = remote
+const { expect } = chai
+
+chai.use(dirtyChai)
 
 const comparePaths = (path1, path2) => {
   if (process.platform === 'win32') {
@@ -23,7 +28,29 @@ describe('remote module', () => {
 
   afterEach(() => closeWindow(w).then(() => { w = null }))
 
+  describe('remote.getGlobal', () => {
+    it('can return custom values', () => {
+      ipcRenderer.send('handle-next-remote-get-global', { test: 'Hello World!' })
+      expect(remote.getGlobal('test')).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', () => {
+      ipcRenderer.send('handle-next-remote-get-global')
+      expect(() => remote.getGlobal('process')).to.throw('Invalid global: process')
+    })
+  })
+
   describe('remote.require', () => {
+    it('can return custom values', () => {
+      ipcRenderer.send('handle-next-remote-require', { test: 'Hello World!' })
+      expect(remote.require('test')).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', () => {
+      ipcRenderer.send('handle-next-remote-require')
+      expect(() => remote.require('electron')).to.throw('Invalid module: electron')
+    })
+
     it('should returns same object for the same module', () => {
       const dialog1 = remote.require('electron')
       const dialog2 = remote.require('electron')

+ 18 - 0
spec/static/main.js

@@ -242,6 +242,24 @@ 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]
+    }
+  })
+})
+
+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]
+    }
+  })
+})
+
 ipcMain.on('set-client-certificate-option', function (event, skip) {
   app.once('select-client-certificate', function (event, webContents, url, list, callback) {
     event.preventDefault()