In the future with multiple types of socket listeners (bare TCP vs. TLS), each will need its own logic for allowing connection. This is moved to the listener level, rather than living at the level of the entire server.
207 lines
7.0 KiB
JavaScript
207 lines
7.0 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 a given
|
|
* 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(server) {
|
|
this._server = server;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
this._server._removeListener(this);
|
|
this._server = null;
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
this._server._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(server) {
|
|
return new SocketListener(server);
|
|
},
|
|
connect(host, port) {
|
|
return socketConnect(host, port);
|
|
}
|
|
};
|