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.
This commit is contained in:
parent
14d44513ff
commit
1676f1a887
2 changed files with 115 additions and 10 deletions
|
@ -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" +
|
||||
|
|
|
@ -108,6 +108,7 @@ 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;
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue