<?php
require('json_rpc.php');
class Demo {
static $login_documentation = "return auth token";
public function login($user, $passwd) {
if (strcmp($user, 'demo') == 0 &&
strcmp($passwd, 'demo') == 0) {
// If you need to handle more than one user you can
// create new token and save it in database
return md5($user . ":" . $passwd);
} else {
throw new Exception("Wrong Password");
}
}
static $ls_documentation = "list directory if token is" .
" valid";
public function ls($token, $path) {
if (strcmp(md5("demo:demo"), $token) == 0) {
if (preg_match("/\.\./", $path)) {
throw new Exception("No directory traversal Dude");
}
$base = preg_replace("/(.*\/).*/", "$1",
$_SERVER["SCRIPT_FILENAME"]);
$path = $base . ($path[0] != '/' ? "/" : "") . $path;
$dir = opendir($path);
while($name = readdir($dir)) {
$fname = $path."/".$name;
if (!is_dir($name) && !is_dir($fname)) {
$list[] = $name;
}
}
closedir($dir);
return $list;
} else {
throw new Exception("Access Denied");
}
}
static $whoami_documentation = "return user information";
public function whoami() {
return array(
"user-agent" => $_SERVER["HTTP_USER_AGENT"],
"your ip" => $_SERVER['REMOTE_ADDR'],
"referer" => $_SERVER["HTTP_REFERER"],
"request uri" => $_SERVER["REQUEST_URI"]);
}
}
handle_json_rpc(new Demo());
?>
NOTE: If you use json_rpc.php file (which handle json-rpc) from the package you have always help function which display all methods or documentation strings if you provide them.
If you want secure login you should generate random token in login JSON-RPC function, and store it in database. For example: md5(time()). You can also use SSL.
See demo in action. login is "demo" and password is "demo". Available command are "ls", "whoami", "help" and "help [rpc-method]"
Hint: if you want full access to the shell you can pass all commands (through AJAX/JSON-RPC) to php passthru function or create CGI script that will call the shell (Some hosting services block access to the shell from php but not from cgi script). You can also implement "cd" bash functionality by storing current path in variable and pass that variable with every command send to the server, you can implement dynamic prompt using the same variable.
(function($) {
$.extend_if_has = function(desc, source, array) {
for (var i=array.length;i--;) {
if (typeof source[array[i]] != 'undefined') {
desc[array[i]] = source[array[i]];
}
}
return desc;
};
$.fn.dterm = function(eval, options) {
var op = $.extend_if_has({}, options,
['greetings', 'prompt',
'history', 'clear',
'exit', 'login',
'name', 'keypress',
'keydown', 'onExit',
'onInit']);
var term = this.append('<div/>').
terminal(eval,op);
if (!options.title) {
options.title = 'JQuery Terminal Emulator';
}
if (options.logoutOnClose) {
options.close = function(e, ui) {
term.logout();
term.clear();
};
} else {
options.close = function(e, ui) {
term.focus(false);
};
}
var self = this;
var dialog = this.dialog($.extend(options, {
resize: function(e, ui) {
var c = self.find('.ui-dialog-content');
term.resize(c.width(), c.height());
},
open: function(e, ui) {
term.focus();
},
closeOnEscape: false
}));
this.terminal = term;
return this;
};
})(jQuery);
Demo Scheme interpreter inside JQuery UI Dialog.
Click on button to with scheme interpreter inside UI Dialog.
Hint: you can use JQuery from scheme. There is defined $ function and functions for all jquery object methods, they names start with coma and they always return jquery object so you can do chaining.
Interpreter allow to use multiline expressions. When you type not finished S-Expresion it change the prompt with set_prompt, contatenate current command with previous not finished expression and when you close last parentises end press enter it evaluate whole expression.
$(function() {
var frames = [];
var LINES_PER_FRAME = 14;
var DELAY = 67;
//star_wars is array of lines from 'js/star_wars.js'
var lines = star_wars.length;
for (var i=0; i>lines; i+=LINES_PER_FRAME) {
frames.push(star_wars.slice(i, i+LINES_PER_FRAME));
}
var stop = false;
//to show greetings after clearing the terminal
function greetings(term) {
term.echo('STAR WARS ASCIIMACTION\n'+
'Simon Jansen (C) 1997 - 2008\n'+
'www.asciimation.co.nz\n\n'+
'type "play" to start animation, '+
'press CTRL+D to stop');
}
function play(term, delay) {
var i = 0;
var next_delay;
if (delay == undefined) {
delay = DELAY;
}
function display() {
if (i == frames.length) {
i = 0;
}
term.clear();
if (frames[i][0].match(/[0-9]+/)) {
next_delay = frames[i][0] * delay;
} else {
next_delay = delay;
}
term.echo(frames[i++].slice(1).join('\n')+'\n');
if (!stop) {
setTimeout(display, next_delay);
} else {
term.clear();
greetings(term);
i = 0;
}
}
display();
}
$('#starwarsterm').terminal(function(command, term){
if (command == 'play') {
term.pause();
stop = false;
play(term);
}
}, {
width: 500,
height: 230,
prompt: 'starwars> ',
greetings: null,
onInit: function(term) {
greetings(term);
},
keypress: function(e, term) {
if (e.which == 100 && e.ctrlKey) {
stop = true;
term.resume();
return false;
}
}
});
});
Ask before execute a command
Someone ask me how to create, command that ask users before executing, and here is the code, it will keep asking until eather yes or no will be entered (or short y/n).
$('#term').terminal(function(command, term) {
if (command == 'foo') {
term.push(function(command) {
if (command.match(/y|yes/i)) {
term.echo('execute your command here');
term.pop();
} else if (command.match(/n|no/i)) {
term.pop();
}
}, {
prompt: 'Are you sure? '
});
}
});
Animation that emulate user typing
Someone else aks if it's posible to create animation like user typing. Here is the code that emulate user typing on initialization of the terminal and before every ajax call, which can finish after animation.
$(function() {
var anim = false;
function typedPrompt(term, message, delay, finish) {
anim = true;
var c = 0;
term.set_prompt('');
var interval = setInterval(function() {
term.insert(message[c++]);
if(c > message.length) {
clearInterval(interval);
// execute in next interval
setTimeout(function() {
// swap command with prompt
term.set_command('')
term.set_prompt(message + ' ');
anim = false
finish && finish();
}, delay);
}
}, delay);
}
$('#term').terminal(function(cmd, term) {
var finish = false;
var msg = "Wait I'm executing ajax call";
typedPrompt(term, msg, 200, function() {
finish = true;
});
var args = {command: cmd};
$.get('commands.php', args, function(result) {
if (finish) {
term.echo(result);
}
});
}, {
name: 'xxx',
greetings: null,
width: 500,
height: 300,
onInit: function(term) {
// first question
var msg = "Wellcome to my terminal";
typedPrompt(term, msg, 200, function() {
// do something after finish
});
},
keydown: function(e) {
//disable keyboard when animating
if (anim) {
return false;
}
}
});
});
Less bash command
Here is implementation of bash less command (not all commands implemented)
var resize = [];
$('<SELECTOR>').terminal(function(command, term) {
if (command.match(/ *less +[^ ]+/)) {
term.pause();
$.ajax({
// leading and trailing spaces and keep those inside argument
url: command.replace(/^\s+|\s+$/g, '').
replace(/^ */, '').split(/(\s+)/).slice(2).join(''),
method: 'GET',
dataType: 'text',
success: function(source) {
term.resume();
var export_data = term.export_view();
var less = true;
source = source.replace(/&/g, '&').
replace(/\[/g, '[').
replace(/\]/g, ']');
var cols = term.cols();
var rows = term.rows();
resize = [];
var lines = source.split('\n');
resize.push(function() {
if (less) {
cols = term.cols();
rows = term.rows();
print();
}
});
var pos = 0;
function print() {
term.clear();
term.echo(lines.slice(pos, pos+rows-1).join('\n'));
}
print();
term.push($.noop, {
keydown: function(e) {
if (term.get_prompt() !== '/') {
if (e.which == 191) {
term.set_prompt('/');
} else if (e.which === 38) { //up
if (pos > 0) {
--pos;
print();
}
} else if (e.which === 40) { //down
if (pos < lines.length-1) {
++pos;
print();
}
} else if (e.which === 34) { // Page up
pos += rows;
if (pos > lines.length-1-rows) {
pos = lines.length-1-rows;
}
print();
} else if (e.which === 33) { // page down
pos -= rows;
if (pos < 0) {
pos = 0;
}
print();
} else if (e.which == 81) { //Q
less = false;
term.pop().import_view(export_data);
}
return false;
} else {
if (e.which === 8 && term.get_command() === '') {
term.set_prompt(':');
} else if (e.which == 13) {
var command = term.get_command();
// basic search find only first
// instance and don't mark the result
if (command.length > 0) {
var regex = new RegExp(command);
for (var i=0; i<lines.length; ++i) {
if (regex.test(lines[i])) {
pos = i;
print();
term.set_command('');
break;
}
}
term.set_command('');
term.set_prompt(':');
}
return false;
}
}
},
prompt: ':'
});
}
});
}
}, {
onResize: function(term) {
for (var i=resize.length;i--;) {
resize[i](term);
}
}
});
Shell
You can check how to create bash shell using terminal on this blog post