From 1676f1a887e1ceb373f84b606dbc71195a4bd2f4 Mon Sep 17 00:00:00 2001 From: KLuka Date: Tue, 16 Jun 2015 18:54:24 +0200 Subject: [PATCH 1/3] 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 From 4f32ae3f2feb46486b2e8063e83e230e4dd83885 Mon Sep 17 00:00:00 2001 From: KLuka Date: Tue, 16 Jun 2015 22:30:02 +0200 Subject: [PATCH 2/3] Message passing examples for embedded shellinabox * Added misc/embedded.html file with more info and actual examples on message passing to or from embedded shellinabox frame. --- misc/embedded.html | 165 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 misc/embedded.html diff --git a/misc/embedded.html b/misc/embedded.html new file mode 100644 index 0000000..a221c21 --- /dev/null +++ b/misc/embedded.html @@ -0,0 +1,165 @@ + + + + + + + + +

+ Embedded Shell In A Box example page. +

+ +

Controls:

+
+ + + + + +
+ +

Session status: ???

+ + + + + +

Terminal output:

+

+
+	
+
+
+

From fb4ebaf01f60227a9bc542d55ca01b561b668f36 Mon Sep 17 00:00:00 2001
From: KLuka 
Date: Wed, 17 Jun 2015 18:11:59 +0200
Subject: [PATCH 3/3] Improved message passing info and examples

---
 misc/embedded.html              | 57 ++++++++++++++++++++++++---------
 shellinabox/shell_in_a_box.jspp |  2 +-
 2 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/misc/embedded.html b/misc/embedded.html
index a221c21..ebdbc8c 100644
--- a/misc/embedded.html
+++ b/misc/embedded.html
@@ -1,24 +1,38 @@
 
 
+	
 	
 
 	
@@ -98,8 +123,8 @@
 		var output  = document.getElementById("output");
 		var session = document.getElementById("session");
 
-		// Add url to our iframe. We do this, only that variable 'url' is used
-		// throughout the whole code.
+		// Add url to our iframe. We do this, only that variable 'url' can be used
+		// throughout the whole code where needed.
 		iframe.src = url;
 
 		document.getElementById("execute").addEventListener("click", function() {
@@ -127,6 +152,8 @@
 				data : 'disable'
 			});
 			iframe.contentWindow.postMessage(message, url);
+			// Clear output window
+			output.innerHTML = '';
 		});
 
 		document.getElementById("session-reload").addEventListener("click", function() {
diff --git a/shellinabox/shell_in_a_box.jspp b/shellinabox/shell_in_a_box.jspp
index 091615b..952decc 100644
--- a/shellinabox/shell_in_a_box.jspp
+++ b/shellinabox/shell_in_a_box.jspp
@@ -389,7 +389,7 @@ ShellInABox.prototype.messageInit = function() {
       }
     }(this), false);
   } else {
-    // For IE8 or lower
+    // For IE8
     if (window.attachEvent) {
       window.attachEvent('onmessage', function(shellInABox) {
         return function(message) {