gecko-dev/toolkit/devtools/security/socket.js
J. Ryan Stinnett c0a6f4090e Bug 1058997 - Part 4: Only one DebuggerServer per loader. r=past
It was never necessary to pass a server reference to the socket listener, since
a given DevTools loader only ever contains a single server instance.
2014-12-01 22:55:56 -08:00

204 lines
6.9 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let { Ci, Cc, CC, Cr } = require("chrome");
let Services = require("Services");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
let { dumpn } = DevToolsUtils;
loader.lazyRequireGetter(this, "DebuggerTransport",
"devtools/toolkit/transport/transport", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
DevToolsUtils.defineLazyGetter(this, "ServerSocket", () => {
return CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initSpecialConnection");
});
DevToolsUtils.defineLazyGetter(this, "UnixDomainServerSocket", () => {
return CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initWithFilename");
});
DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
});
DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
return Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
});
const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
/**
* Connects to a debugger server socket and returns a DebuggerTransport.
*
* @param host string
* The host name or IP address of the debugger server.
* @param port number
* The port number of the debugger server.
*/
function socketConnect(host, port) {
let s = socketTransportService.createTransport(null, 0, host, port, null);
// By default the CONNECT socket timeout is very long, 65535 seconds,
// so that if we race to be in CONNECT state while the server socket is still
// initializing, the connection is stuck in connecting state for 18.20 hours!
s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
// openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
// where the nsISocketTransport gets shutdown in between its instantiation and
// the call to this method.
let transport;
try {
transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
s.openOutputStream(0, 0, 0));
} catch(e) {
DevToolsUtils.reportException("socketConnect", e);
throw e;
}
return transport;
}
/**
* Creates a new socket listener for remote connections to the DebuggerServer.
* This helps contain and organize the parts of the server that may differ or
* are particular to one given listener mechanism vs. another.
*/
function SocketListener() {}
/**
* Prompt the user to accept or decline the incoming connection. This is the
* default implementation that products embedding the debugger server may
* choose to override. A separate security handler can be specified for each
* socket via |allowConnection| on a socket listener instance.
*
* @return true if the connection should be permitted, false otherwise
*/
SocketListener.defaultAllowConnection = () => {
let bundle = Services.strings.createBundle(DBG_STRINGS_URI);
let title = bundle.GetStringFromName("remoteIncomingPromptTitle");
let msg = bundle.GetStringFromName("remoteIncomingPromptMessage");
let disableButton = bundle.GetStringFromName("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.closeAllListeners();
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
}
return false;
};
SocketListener.prototype = {
/**
* Listens on the given port or socket file for remote debugger connections.
*
* @param portOrPath int, string
* If given an integer, the port to listen on.
* Otherwise, the path to the unix socket domain file to listen on.
*/
open: function(portOrPath) {
let flags = Ci.nsIServerSocket.KeepWhenOffline;
// A preference setting can force binding on the loopback interface.
if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
flags |= Ci.nsIServerSocket.LoopbackOnly;
}
try {
let backlog = 4;
let port = Number(portOrPath);
if (port) {
this._socket = new ServerSocket(port, flags, backlog);
} else {
let file = nsFile(portOrPath);
if (file.exists())
file.remove(false);
this._socket = new UnixDomainServerSocket(file, parseInt("666", 8),
backlog);
}
this._socket.asyncListen(this);
} catch (e) {
dumpn("Could not start debugging listener on '" + portOrPath + "': " + e);
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
},
/**
* Closes the SocketListener. Notifies the server to remove the listener from
* the set of active SocketListeners.
*/
close: function() {
this._socket.close();
DebuggerServer._removeListener(this);
},
/**
* Gets the port that a TCP socket listener is listening on, or null if this
* is not a TCP socket (so there is no port).
*/
get port() {
if (!this._socket) {
return null;
}
return this._socket.port;
},
/**
* Prompt the user to accept or decline the incoming connection. The default
* implementation is used unless this is overridden on a particular socket
* listener instance.
*
* @return true if the connection should be permitted, false otherwise
*/
allowConnection: SocketListener.defaultAllowConnection,
// nsIServerSocketListener implementation
onSocketAccepted:
DevToolsUtils.makeInfallible(function(socket, socketTransport) {
if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") &&
!this.allowConnection()) {
return;
}
dumpn("New debugging connection on " +
socketTransport.host + ":" + socketTransport.port);
let input = socketTransport.openInputStream(0, 0, 0);
let output = socketTransport.openOutputStream(0, 0, 0);
let transport = new DebuggerTransport(input, output);
DebuggerServer._onConnection(transport);
}, "SocketListener.onSocketAccepted"),
onStopListening: function(socket, status) {
dumpn("onStopListening, status: " + status);
}
};
// TODO: These high-level entry points will branch based on TLS vs. bare TCP as
// part of bug 1059001.
exports.DebuggerSocket = {
createListener() {
return new SocketListener();
},
connect(host, port) {
return socketConnect(host, port);
}
};