Bug 758696 - Add a dialog to the debugger to deny or allow incoming server connections (Part 1: Firefox); r=rcampbell
--- 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);