Bug 758696 - Add a dialog to the debugger to deny or allow incoming server connections (Part 1: Firefox); r=rcampbell
authorPanos Astithas <past@mozilla.com>
Fri, 01 Jun 2012 18:25:08 +0300
changeset 97738 ae9aa5be8ca2c95726a25777f743608e269001ad
parent 97737 9bad0808a6918b677f5833ffc1019a38f181c6c2
child 97739 af3834c1d9cb3879bd1e7c58d6f1afe212545bea
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs758696
milestone15.0a1
Bug 758696 - Add a dialog to the debugger to deny or allow incoming server connections (Part 1: Firefox); r=rcampbell
browser/devtools/debugger/DebuggerUI.jsm
browser/devtools/debugger/debugger-controller.js
browser/devtools/debugger/test/browser_dbg_createRemote.js
browser/devtools/debugger/test/head.js
browser/locales/en-US/chrome/browser/devtools/debugger.properties
toolkit/devtools/debugger/server/dbg-server.js
toolkit/devtools/debugger/tests/unit/head_dbg.js
toolkit/devtools/debugger/tests/unit/test_attach.js
toolkit/devtools/debugger/tests/unit/test_dbgactor.js
toolkit/devtools/debugger/tests/unit/test_dbgclient_debuggerstatement.js
toolkit/devtools/debugger/tests/unit/test_dbgglobal.js
toolkit/devtools/debugger/tests/unit/test_dbgsocket.js
toolkit/devtools/debugger/tests/unit/test_interrupt.js
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -196,17 +196,18 @@ function DebuggerPane(aDebuggerUI, aTab)
 
 DebuggerPane.prototype = {
 
   /**
    * Initializes the debugger server.
    */
   _initServer: function DP__initServer() {
     if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
+      // Always allow connections from nsIPipe transports.
+      DebuggerServer.init(function () { return true; });
       DebuggerServer.addBrowserActors();
     }
   },
 
   /**
    * Creates and initializes the widgets containing the debugger UI.
    */
   _create: function DP__create() {
@@ -400,24 +401,50 @@ function ChromeDebuggerProcess(aWindow, 
 
 ChromeDebuggerProcess.prototype = {
 
   /**
    * Initializes the debugger server.
    */
   _initServer: function RDP__initServer() {
     if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
+      DebuggerServer.init(this._allowConnection);
       DebuggerServer.addBrowserActors();
     }
     DebuggerServer.closeListener();
     DebuggerServer.openListener(DebuggerPreferences.remotePort, false);
   },
 
   /**
+   * Prompt the user to accept or decline the incoming connection.
+   *
+   * @return true if the connection should be permitted, false otherwise
+   */
+  _allowConnection: function RDP__allowConnection() {
+    let title = L10N.getStr("remoteIncomingPromptTitle");
+    let msg = L10N.getStr("remoteIncomingPromptMessage");
+    let disableButton = L10N.getStr("remoteIncomingPromptDisable");
+    let prompt = Services.prompt;
+    let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK +
+                prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL +
+                prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
+                prompt.BUTTON_POS_1_DEFAULT;
+    let result = prompt.confirmEx(null, title, msg, flags, null, null,
+                                  disableButton, null, { value: false });
+    if (result == 0) {
+      return true;
+    }
+    if (result == 2) {
+      DebuggerServer.closeListener();
+      Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
+    }
+    return false;
+  },
+
+  /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function RDP__initProfile() {
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
       .createInstance(Ci.nsIToolkitProfileService);
 
     let dbgProfileName;
     try {
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -100,17 +100,19 @@ let DebuggerController = {
       this.dispatchEvent("Debugger:Close");
       return false;
     }
 
     // TODO: This is ugly, need to rethink the design for the UI in #751677.
     if (!Prefs.remoteAutoConnect) {
       let prompt = new RemoteDebuggerPrompt();
       let result = prompt.show(!!this._remoteConnectionTimeout);
-      if (!result) {
+      // If the connection was not established before the user canceled the
+      // prompt, close the remote debugger.
+      if (!result && !DebuggerController.activeThread) {
         this.dispatchEvent("Debugger:Close");
         return false;
       }
       Prefs.remoteHost = prompt.uri.host;
       Prefs.remotePort = prompt.uri.port;
     }
 
     // If this debugger is connecting remotely to a server, we need to check
--- a/browser/devtools/debugger/test/browser_dbg_createRemote.js
+++ b/browser/devtools/debugger/test/browser_dbg_createRemote.js
@@ -50,17 +50,17 @@ function test() {
     let iframe = gTab.linkedBrowser.contentWindow.wrappedJSObject.frames[0];
 
     is(iframe.document.title, "Browser Debugger Test Tab", "Found the iframe");
 
     iframe.runDebuggerStatement();
   },
   function beforeTabAdded() {
     if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
+      DebuggerServer.init(function() { return true; });
       DebuggerServer.addBrowserActors();
     }
     DebuggerServer.closeListener();
 
     gAutoConnect = Services.prefs.getBoolPref("devtools.debugger.remote-autoconnect");
     Services.prefs.setBoolPref("devtools.debugger.remote-autoconnect", true);
 
     // Open the listener at some point in the future to test automatic reconnect.
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -14,19 +14,25 @@ let DebuggerTransport = tempScope.Debugg
 let DebuggerClient = tempScope.DebuggerClient;
 let Services = tempScope.Services;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
 const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
 const TAB2_URL = EXAMPLE_URL + "browser_dbg_tab2.html";
 const STACK_URL = EXAMPLE_URL + "browser_dbg_stack.html";
+// Enable remote debugging for the relevant tests.
+let gEnableRemote = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+registerCleanupFunction(function() {
+  Services.prefs.setBoolPref("devtools.debugger.remote-enabled", gEnableRemote);
+});
 
 if (!DebuggerServer.initialized) {
-  DebuggerServer.init();
+  DebuggerServer.init(function () { return true; });
   DebuggerServer.addBrowserActors();
 }
 
 waitForExplicitFinish();
 
 function addTab(aURL, aOnload)
 {
   gBrowser.selectedTab = gBrowser.addTab(aURL);
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -61,16 +61,29 @@ emptyStackText=No stacks to display.
 loadingText=Loading\u2026
 
 # LOCALIZATION NOTE (loadingError):
 # This is the error message that is displayed on failed attempts to load an
 # external resource file.
 # %1$S=URL, %2$S=status code
 loadingError=Error loading %1$S: %2$S
 
+# LOCALIZATION NOTE (remoteIncomingPromptTitle): The title displayed on the
+# dialog that prompts the user to allow the incoming connection.
+remoteIncomingPromptTitle=Incoming Connection
+
+# LOCALIZATION NOTE (remoteIncomingPromptMessage): The message displayed on the
+# dialog that prompts the user to allow the incoming connection.
+remoteIncomingPromptMessage=An incoming request to permit remote debugging connection was detected. A remote client can take complete control over your browser! Allow connection?
+
+# LOCALIZATION NOTE (remoteIncomingPromptDisable): The label displayed on the
+# third button in the incoming connection dialog that lets the user disable the
+# remote debugger server.
+remoteIncomingPromptDisable=Disable
+
 # LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
 # variables pane when there are no variables to display.
 emptyVariablesText=No variables to display.
 
 # LOCALIZATION NOTE (scopeLabel): The text that is displayed in the variables
 # pane as a header for each variable scope (e.g. "Global scope, "With scope",
 # etc.).
 scopeLabel=%S scope
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -54,42 +54,52 @@ const ServerSocket = CC("@mozilla.org/ne
 
 /***
  * Public API
  */
 var DebuggerServer = {
   _listener: null,
   _transportInitialized: false,
   xpcInspector: null,
+  _allowConnection: null,
 
   /**
    * Initialize the debugger server.
+   *
+   * @param function aAllowConnectionCallback
+   *        The embedder-provider callback, that decides whether an incoming
+   *        remote protocol conection should be allowed or refused.
    */
-  init: function DH_init() {
+  init: function DH_init(aAllowConnectionCallback) {
     if (this.initialized) {
       return;
     }
 
     this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
-    this.initTransport();
+    this.initTransport(aAllowConnectionCallback);
     this.addActors("chrome://global/content/devtools/dbg-script-actors.js");
   },
 
   /**
    * Initialize the debugger server's transport variables.  This can be
    * in place of init() for cases where the jsdebugger isn't needed.
+   *
+   * @param function aAllowConnectionCallback
+   *        The embedder-provider callback, that decides whether an incoming
+   *        remote protocol conection should be allowed or refused.
    */
-  initTransport: function DH_initTransport() {
+  initTransport: function DH_initTransport(aAllowConnectionCallback) {
     if (this._transportInitialized) {
       return;
     }
 
     this._connections = {};
     this._nextConnID = 0;
     this._transportInitialized = true;
+    this._allowConnection = aAllowConnectionCallback;
   },
 
   get initialized() { return !!this.xpcInspector; },
 
   /**
    * Load a subscript into the debugging global.
    *
    * @param aURL string A url that will be loaded as a subscript into the
@@ -112,16 +122,19 @@ var DebuggerServer = {
    * Listens on the given port for remote debugger connections.
    *
    * @param aPort int
    *        The port to listen on.
    * @param aLocalOnly bool
    *        If true, server will listen on the loopback device.
    */
   openListener: function DH_openListener(aPort, aLocalOnly) {
+    if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
+      return false;
+    }
     this._checkInit();
 
     if (this._listener) {
       throw "Debugging listener already open.";
     }
 
     try {
       let socket = new ServerSocket(aPort, aLocalOnly, 4);
@@ -204,16 +217,19 @@ var DebuggerServer = {
     }
   },
 
   /**
    * Create a new debugger connection for the given transport.  Called
    * after connectPipe() or after an incoming socket connection.
    */
   _onConnection: function DH_onConnection(aTransport) {
+    if (!this._allowConnection()) {
+      return;
+    }
     let connID = "conn" + this._nextConnID++ + '.';
     let conn = new DebuggerServerConnection(connID, aTransport);
     this._connections[connID] = conn;
 
     // Create a root actor for the connection and send the hello packet.
     conn.rootActor = this.createRootActor(conn);
     conn.addActor(conn.rootActor);
     aTransport.send(conn.rootActor.sayHello());
--- a/toolkit/devtools/debugger/tests/unit/head_dbg.js
+++ b/toolkit/devtools/debugger/tests/unit/head_dbg.js
@@ -7,16 +7,18 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 // Always log packets when running tests. runxpcshelltests.py will throw
 // the output away anyway, unless you give it the --verbose flag.
 Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
 Cu.import("resource:///modules/devtools/dbg-server.jsm");
 Cu.import("resource:///modules/devtools/dbg-client.jsm");
 
 // Convert an nsIScriptError 'aFlags' value into an appropriate string.
 function scriptErrorFlagsToKind(aFlags) {
   var kind;
   if (aFlags & Ci.nsIScriptError.warningFlag)
@@ -118,17 +120,18 @@ function attachTestGlobalClientAndResume
 }
 
 /**
  * Initialize the testing debugger server.
  */
 function initTestDebuggerServer()
 {
   DebuggerServer.addActors("resource://test/testactors.js");
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
 }
 
 function finishClient(aClient)
 {
   aClient.close(function() {
     do_test_finished();
   });
 }
--- a/toolkit/devtools/debugger/tests/unit/test_attach.js
+++ b/toolkit/devtools/debugger/tests/unit/test_attach.js
@@ -3,17 +3,18 @@
 
 var gClient;
 var gDebuggee;
 
 function run_test()
 {
   DebuggerServer.addActors("resource://test/testactors.js");
 
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     getTestGlobalContext(gClient, "test-1", function(aContext) {
       test_attach(aContext);
--- a/toolkit/devtools/debugger/tests/unit/test_dbgactor.js
+++ b/toolkit/devtools/debugger/tests/unit/test_dbgactor.js
@@ -6,17 +6,18 @@ Cu.import("resource:///modules/devtools/
 
 var gClient;
 var gDebuggee;
 
 function run_test()
 {
   DebuggerServer.addActors("resource://test/testactors.js");
 
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.addListener("connected", function(aEvent, aType, aTraits) {
     gClient.request({ to: "root", type: "listContexts" }, function(aResponse) {
       do_check_true('contexts' in aResponse);
--- a/toolkit/devtools/debugger/tests/unit/test_dbgclient_debuggerstatement.js
+++ b/toolkit/devtools/debugger/tests/unit/test_dbgclient_debuggerstatement.js
@@ -6,17 +6,18 @@ Cu.import("resource:///modules/devtools/
 
 var gClient;
 var gDebuggee;
 
 function run_test()
 {
   DebuggerServer.addActors("resource://test/testactors.js");
 
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     getTestGlobalContext(gClient, "test-1", function(aContext) {
       test_attach(aContext);
--- a/toolkit/devtools/debugger/tests/unit/test_dbgglobal.js
+++ b/toolkit/devtools/debugger/tests/unit/test_dbgglobal.js
@@ -9,17 +9,18 @@ function run_test()
   // Should get an exception if we try to interact with DebuggerServer
   // before we initialize it...
   check_except(function() {
     DebuggerServer.openListener(2929, true);
   });
   check_except(DebuggerServer.closeListener);
   check_except(DebuggerServer.connectPipe);
 
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
 
   // These should still fail because we haven't added a createRootActor
   // implementation yet.
   check_except(function() {
     DebuggerServer.openListener(2929, true);
   });
   check_except(DebuggerServer.closeListener);
   check_except(DebuggerServer.connectPipe);
--- a/toolkit/devtools/debugger/tests/unit/test_dbgsocket.js
+++ b/toolkit/devtools/debugger/tests/unit/test_dbgsocket.js
@@ -1,17 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource:///modules/devtools/dbg-server.jsm");
 Cu.import("resource:///modules/devtools/dbg-client.jsm");
 
 function run_test()
 {
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
   DebuggerServer.addActors("resource://test/testactors.js");
 
   add_test(test_socket_conn);
   add_test(test_socket_shutdown);
   add_test(test_pipe_conn);
 
   run_next_test();
 }
--- a/toolkit/devtools/debugger/tests/unit/test_interrupt.js
+++ b/toolkit/devtools/debugger/tests/unit/test_interrupt.js
@@ -3,17 +3,18 @@
 
 var gClient;
 var gDebuggee;
 
 function run_test()
 {
   DebuggerServer.addActors("resource://test/testactors.js");
 
-  DebuggerServer.init();
+  // Allow incoming connections.
+  DebuggerServer.init(function () { return true; });
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     getTestGlobalContext(gClient, "test-1", function(aContext) {
       test_attach(aContext);