/* 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";
// Ensure PSM is initialized to support TLS sockets
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
DevToolsUtils.defineLazyGetter(
lazy, "DevToolsSocketStatus",
() =>
ChromeUtils.importESModule( "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs", // DevToolsSocketStatus is also accessed by non-devtools modules and // should be loaded in the regular / shared global.
{ global: "shared" }
).DevToolsSocketStatus
);
/** * Connects to a devtools server socket. * * @param host string * The host name or IP address of the devtools server. * @param port number * The port number of the devtools server. * @param webSocket boolean (optional) * Whether to use WebSocket protocol to connect. Defaults to false. * @param authenticator Authenticator (optional) * |Authenticator| instance matching the mode in use by the server. * Defaults to a PROMPT instance if not supplied. * @return promise * Resolved to a DebuggerTransport instance.
*/
DebuggerSocket.connect = async function (settings) { // Default to PROMPT |Authenticator| instance if not supplied if (!settings.authenticator) {
settings.authenticator = new (Authenticators.get().Client)();
}
_validateSettings(settings); // eslint-disable-next-line no-shadow const { host, port, authenticator } = settings; const transport = await _getTransport(settings);
await authenticator.authenticate({
host,
port,
transport,
});
transport.connectionSettings = settings; return transport;
};
/** * Validate that the connection settings have been set to a supported configuration.
*/ function _validateSettings(settings) { const { authenticator } = settings;
authenticator.validateSettings(settings);
}
/** * Try very hard to create a DevTools transport, potentially making several * connect attempts in the process. * * @param host string * The host name or IP address of the devtools server. * @param port number * The port number of the devtools server. * @param webSocket boolean (optional) * Whether to use WebSocket protocol to connect to the server. Defaults to false. * @param authenticator Authenticator * |Authenticator| instance matching the mode in use by the server. * Defaults to a PROMPT instance if not supplied. * @return transport DebuggerTransport * A possible DevTools transport (if connection succeeded and streams * are actually alive and working)
*/ var _getTransport = async function (settings) { const { host, port, webSocket } = settings;
if (webSocket) { // Establish a connection and wait until the WebSocket is ready to send and receive const socket = await new Promise((resolve, reject) => { const s = new WebSocket(`ws://${host}:${port}`);
s.onopen = () => resolve(s);
s.onerror = err => reject(err);
});
/** * Make a single attempt to connect and create a DevTools transport. * * @param host string * The host name or IP address of the devtools server. * @param port number * The port number of the devtools server. * @param authenticator Authenticator * |Authenticator| instance matching the mode in use by the server. * Defaults to a PROMPT instance if not supplied. * @return transport DebuggerTransport * A possible DevTools transport (if connection succeeded and streams * are actually alive and working) * @return s nsISocketTransport * Underlying socket transport, in case more details are needed.
*/ var _attemptTransport = async function (settings) { const { authenticator } = settings; // _attemptConnect only opens the streams. Any failures at that stage // aborts the connection process immedidately. const { s, input, output } = await _attemptConnect(settings);
// Check if the input stream is alive.
let alive; try { const results = await _isInputAlive(input);
alive = results.alive;
} catch (e) { // For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach // this block.
input.close();
output.close(); throw e;
}
// The |Authenticator| examines the connection as well and may determine it // should be dropped.
alive =
alive &&
authenticator.validateConnection({
host: settings.host,
port: settings.port,
socket: s,
});
let transport; if (alive) {
transport = new DebuggerTransport(input, output);
} else { // Something went wrong, close the streams.
input.close();
output.close();
}
return { transport, s };
};
/** * Try to connect to a remote server socket. * * If successsful, the socket transport and its opened streams are returned. * Typically, this will only fail if the host / port is unreachable. Other * problems, such as security errors, will allow this stage to succeed, but then * fail later when the streams are actually used. * @return s nsISocketTransport * Underlying socket transport, in case more details are needed. * @return input nsIAsyncInputStream * The socket's input stream. * @return output nsIAsyncOutputStream * The socket's output stream.
*/ var _attemptConnect = async function ({ host, port }) { const s = socketTransportService.createTransport([], host, port, null, null);
// Force disabling IPV6 if we aren't explicitely connecting to an IPv6 address // It fails intermitently on MacOS when opening the Browser Toolbox (bug 1615412) if (!host.includes(":")) {
s.connectionFlags |= Ci.nsISocketTransport.DISABLE_IPV6;
}
// 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. try {
output = s.openOutputStream(0, 0, 0);
} catch (e) {
reject(e);
}
}).catch(e => { if (input) {
input.close();
} if (output) {
output.close();
}
DevToolsUtils.reportException("_attemptConnect", e);
});
};
/** * Creates a new socket listener for remote connections to the DevToolsServer. * This helps contain and organize the parts of the server that may differ or * are particular to one given listener mechanism vs. another. * This can be closed at any later time by calling |close|. * If remote connections are disabled, an error is thrown. * * @param {DevToolsServer} devToolsServer * @param {Object} socketOptions * options of socket as follows * { * authenticator: * Controls the |Authenticator| used, which hooks various socket steps to * implement an authentication policy. It is expected that different use * cases may override pieces of the |Authenticator|. See auth.js. * We set the default |Authenticator|, which is |Prompt|. * discoverable: * Controls whether this listener is announced via the service discovery * mechanism. Defaults is false. * fromBrowserToolbox: * Should only be passed when opening a socket for a Browser Toolbox * session. DevToolsSocketStatus will track the socket separately to * avoid triggering the visual cue in the URL bar. * portOrPath: * The port or path to listen on. * If given an integer, the port to listen on. Use -1 to choose any available * port. Otherwise, the path to the unix socket domain file to listen on. * Defaults is null. * webSocket: * Whether to use WebSocket protocol. Defaults is false. * }
*/ function SocketListener(devToolsServer, socketOptions) { this._devToolsServer = devToolsServer;
// Set socket options with default value this._socketOptions = {
authenticator:
socketOptions.authenticator || new (Authenticators.get().Server)(),
discoverable: !!socketOptions.discoverable,
fromBrowserToolbox: !!socketOptions.fromBrowserToolbox,
portOrPath: socketOptions.portOrPath || null,
webSocket: !!socketOptions.webSocket,
};
EventEmitter.decorate(this);
}
SocketListener.prototype = {
get authenticator() { returnthis._socketOptions.authenticator;
},
get discoverable() { returnthis._socketOptions.discoverable;
},
get fromBrowserToolbox() { returnthis._socketOptions.fromBrowserToolbox;
},
get portOrPath() { returnthis._socketOptions.portOrPath;
},
get webSocket() { returnthis._socketOptions.webSocket;
},
/** * Validate that all options have been set to a supported configuration.
*/
_validateOptions() { if (this.portOrPath === null) { thrownew Error("Must set a port / path to listen on.");
} if (this.discoverable && !Number(this.portOrPath)) { thrownew Error("Discovery only supported for TCP sockets.");
}
},
/** * Listens on the given port or socket file for remote debugger connections.
*/
open() { this._validateOptions(); this._devToolsServer.addSocketListener(this);
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;
}
const self = this; return (async function () { const backlog = 4;
self._socket = self._createSocketInstance(); if (self.isPortBased) { const port = Number(self.portOrPath);
self._socket.initSpecialConnection(port, flags, backlog);
} elseif (self.portOrPath.startsWith("/")) { const file = nsFile(self.portOrPath); if (file.exists()) {
file.remove(false);
}
self._socket.initWithFilename(file, parseInt("666", 8), backlog);
} else { // Path isn't absolute path, so we use abstract socket address
self._socket.initWithAbstractAddress(self.portOrPath, backlog);
}
self._socket.asyncListen(self);
dumpn("Socket listening on: " + (self.port || self.portOrPath));
})()
.then(() => {
lazy.DevToolsSocketStatus.notifySocketOpened({
fromBrowserToolbox: self.fromBrowserToolbox,
}); this._advertise();
})
.catch(e => {
dumpn( "Could not start debugging listener on '" + this.portOrPath + "': " +
e
); this.close();
});
},
_advertise() { if (!this.discoverable || !this.port) { return;
}
/** * Closes the SocketListener. Notifies the server to remove the listener from * the set of active SocketListeners.
*/
close() { if (this.discoverable && this.port) {
discovery.removeService("devtools");
} if (this._socket) { this._socket.close(); this._socket = null;
get host() { if (!this._socket) { returnnull;
} if (Services.prefs.getBoolPref("devtools.debugger.force-local")) { return"127.0.0.1";
} return"0.0.0.0";
},
/** * Gets whether this listener uses a port number vs. a path.
*/
get isPortBased() { return !!Number(this.portOrPath);
},
/** * 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.isPortBased || !this._socket) { returnnull;
} returnthis._socket.port;
},
/** * A |ServerSocketConnection| is created by a |SocketListener| for each accepted * incoming socket.
*/ function ServerSocketConnection(listener, socketTransport) { this._listener = listener; this._socketTransport = socketTransport; this._handle();
EventEmitter.decorate(this);
}
ServerSocketConnection.prototype = {
get authentication() { returnthis._listener.authenticator.mode;
},
get host() { returnthis._socketTransport.host;
},
get port() { returnthis._socketTransport.port;
},
get address() { returnthis.host + ":" + this.port;
},
get server() { const server = {
host: this._listener.host,
port: this._listener.port,
}; return server;
},
/** * This is the main authentication workflow. If any pieces reject a promise, * the connection is denied. If the entire process resolves successfully, * the connection is finally handed off to the |DevToolsServer|.
*/
async _handle() {
dumpn("Debugging connection starting authentication on " + this.address); try {
await this._createTransport();
await this._authenticate(); this.allow();
} catch (e) { this.deny(e);
}
},
/** * We need to open the streams early on, as that is required in the case of * TLS sockets to keep the handshake moving.
*/
async _createTransport() { const input = this._socketTransport.openInputStream(0, 0, 0); const output = this._socketTransport.openOutputStream(0, 0, 0);
if (this._listener.webSocket) { const socket = await WebSocketServer.accept( this._socketTransport,
input,
output
); this._transport = new WebSocketDebuggerTransport(socket);
} else { this._transport = new DebuggerTransport(input, output);
}
// Start up the transport to observe the streams in case they are closed // early. This allows us to clean up our state as well. this._transport.hooks = {
onTransportClosed: reason => { this.deny(reason);
},
}; this._transport.ready();
},
// If result is fine, we can stop here if (
result === AuthenticationResult.ALLOW ||
result === AuthenticationResult.ALLOW_PERSIST
) { return;
}
if (result === AuthenticationResult.DISABLE_ALL) { this._listener._devToolsServer.closeAllSocketListeners();
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
}
// If we got an error (DISABLE_ALL, DENY, …), let's throw a NS_ERROR_CONNECTION_REFUSED // exception throw Components.Exception("", Cr.NS_ERROR_CONNECTION_REFUSED);
},
deny(result) { if (this._destroyed) { return;
}
let errorName = result; for (const name in Cr) { if (Cr[name] === result) {
errorName = name; break;
}
}
dumpn( "Debugging connection denied on " + this.address + " (" + errorName + ")"
); if (this._transport) { this._transport.hooks = null; this._transport.close(result);
} this._socketTransport.close(result); this.destroy();
},
allow() { if (this._destroyed) { return;
}
dumpn("Debugging connection allowed on " + this.address); this.emit("allowed", this._transport); this.destroy();
},
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.17Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.