Merge pull request #317 from KLuka/messages
Message passing to embedded shellinabox * Added ability to pass messages to or from shellinabox embedded iframe. * Added example file.
This commit is contained in:
commit
8f38e7873b
3 changed files with 307 additions and 10 deletions
192
misc/embedded.html
Normal file
192
misc/embedded.html
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
##
|
||||||
|
# Example page with embedded Shell In A Box.
|
||||||
|
#
|
||||||
|
|
||||||
|
On this page we can see how Shell In A Box can be embedded in another page and
|
||||||
|
how can we comunicate with it.
|
||||||
|
|
||||||
|
##
|
||||||
|
# Server Side
|
||||||
|
#
|
||||||
|
|
||||||
|
For communication with Shell In A Box we need to set '-m' (messages-origin)
|
||||||
|
command line option with appropriate messages origin. Origin should be set to
|
||||||
|
URL of parent (this) window. If origin is set to '*' Shell In A Box won't checki
|
||||||
|
origin on received messages. This is usually unsafe option.
|
||||||
|
|
||||||
|
Command line example:
|
||||||
|
|
||||||
|
shellinaboxd -p 4200 -m 'https://192.168.1.150'
|
||||||
|
|
||||||
|
##
|
||||||
|
# Client Side
|
||||||
|
#
|
||||||
|
|
||||||
|
Shell In A Box accepts messages formated as JSON strings with 'type' and 'data'
|
||||||
|
fields. Messages with same format can be passed back to parent (this) window.
|
||||||
|
|
||||||
|
Message example:
|
||||||
|
|
||||||
|
var message = JSON.stringify({
|
||||||
|
type : "message type",
|
||||||
|
data : "additional data"
|
||||||
|
});
|
||||||
|
|
||||||
|
Messages are passed with function postMessage() and are received in "message"
|
||||||
|
events.
|
||||||
|
|
||||||
|
Following types of message can be sent to shellinabox:
|
||||||
|
|
||||||
|
input - writes content of data field to terminal
|
||||||
|
output - enables passing of output to parent window (data field must be set
|
||||||
|
to enable, disable or toggle)
|
||||||
|
session - request sessions status
|
||||||
|
|
||||||
|
Following types of messages can be received from shellinabox:
|
||||||
|
|
||||||
|
output - data field contains terminal output
|
||||||
|
session - data field contains session status (active or closed)
|
||||||
|
|
||||||
|
Example for passing command to Shell In A Box frame:
|
||||||
|
|
||||||
|
iframe.contentWindow.postMessage(JSON.stringify({
|
||||||
|
type : "input",
|
||||||
|
data : "ls -l\n"
|
||||||
|
}), "https://192.168.1.150:4200");
|
||||||
|
|
||||||
|
Please note that message passing and JSON operations are only supported on moderen
|
||||||
|
browsers.
|
||||||
|
|
||||||
|
##
|
||||||
|
# Info
|
||||||
|
#
|
||||||
|
|
||||||
|
For working examples please see HTML and JS code bellow...
|
||||||
|
|
||||||
|
For more info and browser limitations on iframe message passing please check:
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
|
||||||
|
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
#shell, #output {
|
||||||
|
width: 640px;
|
||||||
|
height: 300px;
|
||||||
|
margin: 20px 10px;
|
||||||
|
}
|
||||||
|
#output {
|
||||||
|
overflow: scroll;
|
||||||
|
border: 2px solid #999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
Embedded Shell In A Box example page.
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p>Controls:</p>
|
||||||
|
<div>
|
||||||
|
<input type="text" id="input"></input>
|
||||||
|
<input type="button" id="execute" value="Execute"></input>
|
||||||
|
<input type="button" id="output-enable" value="Output Enable"></input>
|
||||||
|
<input type="button" id="output-disable" value="Output Disable"></input>
|
||||||
|
<input type="button" id="session-reload" value="Session Status"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="session">Session status: ???</p>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Embedded shellinabox. In our case src attribute will be added with help
|
||||||
|
of JS. -->
|
||||||
|
<iframe id="shell" src=""></iframe>
|
||||||
|
|
||||||
|
<!-- Ouput -->
|
||||||
|
<p>Terminal output:</p>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// Shellinabox url
|
||||||
|
var url = "https://192.168.1.150:4200";
|
||||||
|
|
||||||
|
var input = document.getElementById("input");
|
||||||
|
var iframe = document.getElementById("shell");
|
||||||
|
var output = document.getElementById("output");
|
||||||
|
var session = document.getElementById("session");
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
// Send input to shellinabox
|
||||||
|
var message = JSON.stringify({
|
||||||
|
type : 'input',
|
||||||
|
data : input.value + '\n'
|
||||||
|
});
|
||||||
|
iframe.contentWindow.postMessage(message, url);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("output-enable").addEventListener("click", function() {
|
||||||
|
// Enable output replay from shellinabox iframe
|
||||||
|
var message = JSON.stringify({
|
||||||
|
type : 'output',
|
||||||
|
data : 'enable'
|
||||||
|
});
|
||||||
|
iframe.contentWindow.postMessage(message, url);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("output-disable").addEventListener("click", function() {
|
||||||
|
// Disable output replay from shellinabox iframe
|
||||||
|
var message = JSON.stringify({
|
||||||
|
type : 'output',
|
||||||
|
data : 'disable'
|
||||||
|
});
|
||||||
|
iframe.contentWindow.postMessage(message, url);
|
||||||
|
// Clear output window
|
||||||
|
output.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("session-reload").addEventListener("click", function() {
|
||||||
|
// Request shellianbox session status
|
||||||
|
var message = JSON.stringify({
|
||||||
|
type : 'session'
|
||||||
|
});
|
||||||
|
iframe.contentWindow.postMessage(message, url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Receive response from shellinabox
|
||||||
|
window.addEventListener("message", function(message) {
|
||||||
|
|
||||||
|
// Allow messages only from shellinabox
|
||||||
|
if (message.origin !== url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response according to response type
|
||||||
|
var decoded = JSON.parse(message.data);
|
||||||
|
switch (decoded.type) {
|
||||||
|
case "output" :
|
||||||
|
// Append new output
|
||||||
|
output.innerHTML = output.innerHTML + decoded.data;
|
||||||
|
break;
|
||||||
|
case "session" :
|
||||||
|
// Reload session status
|
||||||
|
session.innerHTML = 'Session status: ' + decoded.data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -109,12 +109,15 @@ function ShellInABox(url, container) {
|
||||||
this.pendingKeys = '';
|
this.pendingKeys = '';
|
||||||
this.keysInFlight = false;
|
this.keysInFlight = false;
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
|
this.replayOutput = false;
|
||||||
this.superClass.constructor.call(this, container);
|
this.superClass.constructor.call(this, container);
|
||||||
|
|
||||||
|
|
||||||
// We have to initiate the first XMLHttpRequest from a timer. Otherwise,
|
// We have to initiate the first XMLHttpRequest from a timer. Otherwise,
|
||||||
// Chrome never realizes that the page has loaded.
|
// Chrome never realizes that the page has loaded.
|
||||||
setTimeout(function(shellInABox) {
|
setTimeout(function(shellInABox) {
|
||||||
return function() {
|
return function() {
|
||||||
|
shellInABox.messageInit();
|
||||||
shellInABox.sendRequest();
|
shellInABox.sendRequest();
|
||||||
};
|
};
|
||||||
}(this), 1);
|
}(this), 1);
|
||||||
|
@ -192,6 +195,9 @@ ShellInABox.prototype.onReadyStateChange = function(request) {
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
var response = eval('(' + request.responseText + ')');
|
var response = eval('(' + request.responseText + ')');
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
|
if (this.replayOutput) {
|
||||||
|
this.messageReplay('output', response.data);
|
||||||
|
}
|
||||||
this.vt100(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
|
||||||
|
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() {
|
ShellInABox.prototype.about = function() {
|
||||||
alert("Shell In A Box version " + VERSION +
|
alert("Shell In A Box version " + VERSION +
|
||||||
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
|
|
|
@ -108,6 +108,7 @@ static int noBeep = 0;
|
||||||
static int numericHosts = 0;
|
static int numericHosts = 0;
|
||||||
static int enableSSL = 1;
|
static int enableSSL = 1;
|
||||||
static int enableSSLMenu = 1;
|
static int enableSSLMenu = 1;
|
||||||
|
static char *messagesOrigin = NULL;
|
||||||
static int linkifyURLs = 1;
|
static int linkifyURLs = 1;
|
||||||
static char *certificateDir;
|
static char *certificateDir;
|
||||||
static int certificateFd = -1;
|
static int certificateFd = -1;
|
||||||
|
@ -677,11 +678,16 @@ static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
|
||||||
"disableSSLMenu = %s;\n"
|
"disableSSLMenu = %s;\n"
|
||||||
"suppressAllAudio = %s;\n"
|
"suppressAllAudio = %s;\n"
|
||||||
"linkifyURLs = %d;\n"
|
"linkifyURLs = %d;\n"
|
||||||
"userCSSList = %s;\n\n",
|
"userCSSList = %s;\n"
|
||||||
|
"serverMessagesOrigin = %s%s%s;\n\n",
|
||||||
enableSSL ? "true" : "false",
|
enableSSL ? "true" : "false",
|
||||||
!enableSSLMenu ? "true" : "false",
|
!enableSSLMenu ? "true" : "false",
|
||||||
noBeep ? "true" : "false",
|
noBeep ? "true" : "false",
|
||||||
linkifyURLs, userCSSString);
|
linkifyURLs,
|
||||||
|
userCSSString,
|
||||||
|
messagesOrigin ? "'" : "",
|
||||||
|
messagesOrigin ? messagesOrigin : "false",
|
||||||
|
messagesOrigin ? "'" : "");
|
||||||
free(userCSSString);
|
free(userCSSString);
|
||||||
int stateVarsLength = strlen(stateVars);
|
int stateVarsLength = strlen(stateVars);
|
||||||
int contentLength = stateVarsLength +
|
int contentLength = stateVarsLength +
|
||||||
|
@ -773,6 +779,7 @@ static void usage(void) {
|
||||||
" --localhost-only only listen on 127.0.0.1\n"
|
" --localhost-only only listen on 127.0.0.1\n"
|
||||||
" --no-beep suppress all audio output\n"
|
" --no-beep suppress all audio output\n"
|
||||||
" -n, --numeric do not resolve hostnames\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"
|
" --pidfile=PIDFILE publish pid of daemon process\n"
|
||||||
" -p, --port=PORT select a port (default: %d)\n"
|
" -p, --port=PORT select a port (default: %d)\n"
|
||||||
" -s, --service=SERVICE define one or more services\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));
|
check(cssStyleSheet = strdup(stylesStart));
|
||||||
|
|
||||||
for (;;) {
|
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[] = {
|
static struct option options[] = {
|
||||||
{ "help", 0, 0, 'h' },
|
{ "help", 0, 0, 'h' },
|
||||||
{ "background", 2, 0, 'b' },
|
{ "background", 2, 0, 'b' },
|
||||||
|
@ -877,6 +884,7 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
{ "localhost-only", 0, 0, 0 },
|
{ "localhost-only", 0, 0, 0 },
|
||||||
{ "no-beep", 0, 0, 0 },
|
{ "no-beep", 0, 0, 0 },
|
||||||
{ "numeric", 0, 0, 'n' },
|
{ "numeric", 0, 0, 'n' },
|
||||||
|
{ "messages-origin", 1, 0, 'm' },
|
||||||
{ "pidfile", 1, 0, 0 },
|
{ "pidfile", 1, 0, 0 },
|
||||||
{ "port", 1, 0, 'p' },
|
{ "port", 1, 0, 'p' },
|
||||||
{ "service", 1, 0, 's' },
|
{ "service", 1, 0, 's' },
|
||||||
|
@ -1054,6 +1062,15 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
} else if (!idx--) {
|
} else if (!idx--) {
|
||||||
// Numeric
|
// Numeric
|
||||||
numericHosts = 1;
|
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--) {
|
} else if (!idx--) {
|
||||||
// Pidfile
|
// Pidfile
|
||||||
if (cgi) {
|
if (cgi) {
|
||||||
|
@ -1346,6 +1363,7 @@ int main(int argc, char * const argv[]) {
|
||||||
free(services);
|
free(services);
|
||||||
free(certificateDir);
|
free(certificateDir);
|
||||||
free(cgiSessionKey);
|
free(cgiSessionKey);
|
||||||
|
free(messagesOrigin);
|
||||||
if (pidfile) {
|
if (pidfile) {
|
||||||
// As a convenience, remove the pidfile, if it is still the version that
|
// As a convenience, remove the pidfile, if it is still the version that
|
||||||
// we wrote. In general, pidfiles are not expected to be incredibly
|
// we wrote. In general, pidfiles are not expected to be incredibly
|
||||||
|
|
Loading…
Reference in a new issue