From 1676f1a887e1ceb373f84b606dbc71195a4bd2f4 Mon Sep 17 00:00:00 2001 From: KLuka Date: Tue, 16 Jun 2015 18:54:24 +0200 Subject: [PATCH] Message passing support for embedded shellinabox * Added basic support for message passing to or from embedded shellinabox iframe. Now we can write to terminal, read the terminal output and request session status from parent window. * This functionality must be enabled with command line parameter "--messages-origin ORIGIN". Value ORIGIN, which is compared with message against received message origin, must be set to specific url, or to "*" to allow messages from any origin. --- shellinabox/shell_in_a_box.jspp | 87 +++++++++++++++++++++++++++++++++ shellinabox/shellinaboxd.c | 38 ++++++++++---- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/shellinabox/shell_in_a_box.jspp b/shellinabox/shell_in_a_box.jspp index c7b61cb..091615b 100644 --- a/shellinabox/shell_in_a_box.jspp +++ b/shellinabox/shell_in_a_box.jspp @@ -109,12 +109,15 @@ function ShellInABox(url, container) { this.pendingKeys = ''; this.keysInFlight = false; this.connected = false; + this.replayOutput = false; this.superClass.constructor.call(this, container); + // We have to initiate the first XMLHttpRequest from a timer. Otherwise, // Chrome never realizes that the page has loaded. setTimeout(function(shellInABox) { return function() { + shellInABox.messageInit(); shellInABox.sendRequest(); }; }(this), 1); @@ -192,6 +195,9 @@ ShellInABox.prototype.onReadyStateChange = function(request) { this.connected = true; var response = eval('(' + request.responseText + ')'); if (response.data) { + if (this.replayOutput) { + this.messageReplay('output', response.data); + } this.vt100(response.data); } @@ -360,6 +366,87 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) { }; +ShellInABox.prototype.messageInit = function() { + + // Test if server option for iframe message passing was set. + if (!serverMessagesOrigin) { + return; + } + + // Test for browser support of this feature. JSON class functionality is + // also needed because some older IE browsers, support only string passing + // and we don't want to use unsafe eval() function in this case. + if (!window.postMessage || !window.JSON || + !window.JSON.parse || !window.JSON.stringify) { + return; + } + + // Install event listener. + if (window.addEventListener) { + window.addEventListener('message', function(shellInABox) { + return function(message) { + shellInABox.messageReceive(message); + } + }(this), false); + } else { + // For IE8 or lower + if (window.attachEvent) { + window.attachEvent('onmessage', function(shellInABox) { + return function(message) { + shellInABox.messageReceive(message); + } + }(this)); + } + } + +}; + +ShellInABox.prototype.messageReceive = function (message) { + + // Check for message origin if needed. + if (serverMessagesOrigin !== "*") { + if (serverMessagesOrigin !== message.origin) { + return; + } + } + + // Remember replay information. + if (!this.replaySource || !this.replayOrigin) { + this.replaySource = message.source; + this.replayOrigin = message.origin; + } + + // Handle received message. + var decoded = JSON.parse(message.data); + switch (decoded.type) { + case 'input' : + // Input received data to terminal. + this.keysPressed(decoded.data); + break; + case 'output' : + // Enable, disable or toggle passing terminal output to parent window. + if (decoded.data === 'enable') { + this.replayOutput = true; + } else if (decoded.data === 'disable') { + this.replayOutput = false; + } else if (decoded.data === 'toggle') { + this.replayOutput = !this.replayOutput; + } + break; + case 'session': + // Replay with session status. + this.messageReplay('session', this.session ? 'alive' : 'closed'); + break; + } +}; + +ShellInABox.prototype.messageReplay = function(type, data) { + if (this.replaySource && this.replayOrigin) { + var encoded = JSON.stringify({ type : type, data : data }); + this.replaySource.postMessage(encoded, this.replayOrigin); + } +}; + ShellInABox.prototype.about = function() { alert("Shell In A Box version " + VERSION + "\nCopyright 2008-2010 by Markus Gutschke\n" + diff --git a/shellinabox/shellinaboxd.c b/shellinabox/shellinaboxd.c index edd5455..c6f150e 100644 --- a/shellinabox/shellinaboxd.c +++ b/shellinabox/shellinaboxd.c @@ -103,14 +103,15 @@ static int port; static int portMin; static int portMax; -static int localhostOnly = 0; -static int noBeep = 0; -static int numericHosts = 0; -static int enableSSL = 1; -static int enableSSLMenu = 1; -static int linkifyURLs = 1; +static int localhostOnly = 0; +static int noBeep = 0; +static int numericHosts = 0; +static int enableSSL = 1; +static int enableSSLMenu = 1; +static char *messagesOrigin = NULL; +static int linkifyURLs = 1; static char *certificateDir; -static int certificateFd = -1; +static int certificateFd = -1; static HashMap *externalFiles; static Server *cgiServer; static char *cgiSessionKey; @@ -677,11 +678,16 @@ static int shellInABoxHttpHandler(HttpConnection *http, void *arg, "disableSSLMenu = %s;\n" "suppressAllAudio = %s;\n" "linkifyURLs = %d;\n" - "userCSSList = %s;\n\n", + "userCSSList = %s;\n" + "serverMessagesOrigin = %s%s%s;\n\n", enableSSL ? "true" : "false", !enableSSLMenu ? "true" : "false", noBeep ? "true" : "false", - linkifyURLs, userCSSString); + linkifyURLs, + userCSSString, + messagesOrigin ? "'" : "", + messagesOrigin ? messagesOrigin : "false", + messagesOrigin ? "'" : ""); free(userCSSString); int stateVarsLength = strlen(stateVars); int contentLength = stateVarsLength + @@ -773,6 +779,7 @@ static void usage(void) { " --localhost-only only listen on 127.0.0.1\n" " --no-beep suppress all audio output\n" " -n, --numeric do not resolve hostnames\n" + " -m, --messages-origin=ORIGIN allow iframe message passing from origin\n" " --pidfile=PIDFILE publish pid of daemon process\n" " -p, --port=PORT select a port (default: %d)\n" " -s, --service=SERVICE define one or more services\n" @@ -862,7 +869,7 @@ static void parseArgs(int argc, char * const argv[]) { check(cssStyleSheet = strdup(stylesStart)); for (;;) { - static const char optstring[] = "+hb::c:df:g:np:s:tqu:v"; + static const char optstring[] = "+hb::c:df:g:nm:p:s:tqu:v"; static struct option options[] = { { "help", 0, 0, 'h' }, { "background", 2, 0, 'b' }, @@ -877,6 +884,7 @@ static void parseArgs(int argc, char * const argv[]) { { "localhost-only", 0, 0, 0 }, { "no-beep", 0, 0, 0 }, { "numeric", 0, 0, 'n' }, + { "messages-origin", 1, 0, 'm' }, { "pidfile", 1, 0, 0 }, { "port", 1, 0, 'p' }, { "service", 1, 0, 's' }, @@ -1054,6 +1062,15 @@ static void parseArgs(int argc, char * const argv[]) { } else if (!idx--) { // Numeric numericHosts = 1; + } else if (!idx--) { + // Messages origin + if (messagesOrigin) { + fatal("Duplicated \"--messages-origin\" option."); + } + if (!optarg || !*optarg) { + fatal("Option \"--messages-origin\" expects an argument."); + } + check(messagesOrigin = strdup(optarg)); } else if (!idx--) { // Pidfile if (cgi) { @@ -1346,6 +1363,7 @@ int main(int argc, char * const argv[]) { free(services); free(certificateDir); free(cgiSessionKey); + free(messagesOrigin); if (pidfile) { // As a convenience, remove the pidfile, if it is still the version that // we wrote. In general, pidfiles are not expected to be incredibly