From 853afd8c57f6e3f2aa2af4c5eb8c9709979b80be Mon Sep 17 00:00:00 2001 From: BlackLight Date: Tue, 21 Dec 2010 22:47:39 +0100 Subject: [PATCH] First commit --- blash.css | 53 +++++++++ blash.js | 193 +++++++++++++++++++++++++++++++ blash.json | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 16 +++ 4 files changed, 586 insertions(+) create mode 100644 blash.css create mode 100644 blash.js create mode 100644 blash.json create mode 100644 index.html diff --git a/blash.css b/blash.css new file mode 100644 index 0000000..a3a5b17 --- /dev/null +++ b/blash.css @@ -0,0 +1,53 @@ +body +{ + background-color : black; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; +} + +input.promptInput +{ + background-color : black; + border : 0; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; +} + +div#blashWindow +{ + width : 600px; + height : 400px; + margin : auto; + padding : 10px; + border : 1px solid #888; + overflow : auto; +} + +a.bannerLink +{ + color : white; +} + +span.directory +{ + color : #2553ee; +} + +.file +{ + color : #4f4; +} + +span.syntax +{ + color : white; + font-weight : bold; +} + +span.brief +{ + color : green; +} + diff --git a/blash.js b/blash.js new file mode 100644 index 0000000..b795d33 --- /dev/null +++ b/blash.js @@ -0,0 +1,193 @@ +var shell = null; + +function blash () +{ + /************ ATTRIBUTES **************/ + /** Object containing the parsed JSON configuration object */ + this.json = {}; + + /** Shell window object */ + this.window = document.getElementById ( "blashWindow" ); + + /** Escape sequences to be parsed in the prompt text */ + this.promptSequences = new Array(); + + /** Array containing the history of given commands */ + this.history = new Array(); + + /** Index to the current history element */ + this.history_index = -1; + + /** Current path */ + this.path = '/'; + + /** Element containing the output of the command */ + this.cmdOut = document.getElementById ( "blashCmdOut" ); + + /** Element containing the text of the prompt */ + this.promptText = document.getElementById ( "promptText" ); + + /** Input field used as prompt */ + this.prompt = document.getElementsByName ( "blashPrompt" )[0]; + + /** Counter of the open tags when replacing the colours in the command prompt */ + this.__open_spans = 0; + /**************************************/ + + this.prompt.focus(); + + var json_config = window.location.href; + json_config = json_config.replace ( /\/([a-zA-Z\.]+)$/, '/blash.json' ); + + var http = new XMLHttpRequest(); + http.open ( "GET", json_config, true ); + + http.onreadystatechange = function () + { + if ( http.readyState == 4 && http.status == 200 ) + { + shell.json = eval ( '(' + http.responseText + ')' ); + + shell.promptText.innerHTML = ( shell.json.promptText ) ? shell.json.promptText : "[%n@%m %W] $ "; + shell.promptText.innerHTML = shell.unescapePrompt ( promptText.innerHTML, shell.json.promptSequences ); + + if ( shell.json.banner.length > 0 ) + { + var banner = document.createElement ( "span" ); + banner.setAttribute ( "id", "banner" ); + banner.innerHTML = shell.json.banner; + shell.window.insertBefore ( banner, shell.promptText ); + } + } + } + + http.send ( null ); + + this.getKey = function ( e ) + { + var evt = ( window.event ) ? window.event : e; + var key = ( evt.charCode ) ? evt.charCode : evt.keyCode; + + if ( key == 13 || key == 10 ) + { + if ( this.prompt.value.length != 0 ) + { + this.prompt.value.match ( /^([^\s]+)\s*(.*)$/ ); + var cmd = RegExp.$1; + var arg = RegExp.$2; + var cmd_found = false; + this.history.push ( this.prompt.value ); + this.history_index = -1; + + for ( i=0; i < this.json.commands.length && !cmd_found; i++ ) + { + if ( this.json.commands[i].name == cmd ) + { + cmd_found = true; + var out = this.json.commands[i].action ( arg ); + + if ( out.length > 0 ) + { + this.cmdOut.innerHTML = out; + } + } + } + + if ( !cmd_found ) + { + this.cmdOut.innerHTML = this.json.shellName + ": command not found: " + cmd + '
'; + } + + var value = this.prompt.value; + var out = this.cmdOut.innerHTML; + + this.window.removeChild ( this.prompt ); + this.window.removeChild ( this.cmdOut ); + this.window.innerHTML += value + '
' + out + this.promptText.innerHTML; + + this.prompt = document.createElement ( 'input' ); + this.prompt.setAttribute ( 'name', 'blashPrompt' ); + this.prompt.setAttribute ( 'type', 'text' ); + this.prompt.setAttribute ( 'class', 'promptInput' ); + this.prompt.setAttribute ( 'autocomplete', 'off' ); + this.prompt.setAttribute ( 'onkeyup', 'shell.getKey ( event )' ); + + this.cmdOut = document.createElement ( 'div' ); + this.cmdOut.setAttribute ( 'id', 'blashCmdOut' ); + this.cmdOut.setAttribute ( 'class', 'blashCmdOut' ); + this.cmdOut.innerHTML = '
'; + + this.window.appendChild ( this.prompt ); + this.window.appendChild ( this.cmdOut ); + this.prompt.focus(); + } + } else if ( key == 38 || key == 40 ) { + if ( key == 38 ) + { + if ( this.history_index < 0 ) + { + this.history_index = this.history.length - 1; + this.prompt.value = this.history[ this.history_index ]; + } else if ( this.history_index == 0 ) { + // We're already on the first history element + } else { + this.history_index--; + this.prompt.value = this.history[ this.history_index ]; + } + } else if ( key == 40 ) { + if ( this.history_index < 0 ) + { + // We're already on the last element, don't do anything + } else if ( this.history_index == this.history.length - 1 ) { + this.prompt.value = ''; + } else { + this.history_index++; + this.prompt.value = this.history[ this.history_index ]; + } + } + + this.prompt.focus(); + } + } + + this.unescapePrompt = function ( prompt, sequences ) + { + var re = new RegExp ( "([^\]?)#\{([0-9]+)\}" ); + + while ( prompt.match ( re )) + { + if ( this.__open_spans > 0 ) + { + prompt = prompt.replace ( re, RegExp.$1 + "
" ); + } else { + prompt = prompt.replace ( re, RegExp.$1 + "" ); + this.__open_spans++; + } + } + + if ( this.__open_spans > 0 ) + { + prompt = prompt + ""; + } + + for ( i=0; i < sequences.length; i++ ) + { + prompt = this.unescapePromptSequence ( prompt, sequences[i].sequence, sequences[i].text(), sequences[i].default_text ); + } + + return prompt; + } + + this.unescapePromptSequence = function ( prompt, sequence, text, default_text ) + { + var re = new RegExp ( "([^\]?)" + sequence, "g" ); + + if ( prompt.match ( re )) + { + prompt = prompt.replace ( re, (( text ) ? RegExp.$1 + text : RegExp.$1 + default_text )); + } + + return prompt; + } +} + diff --git a/blash.json b/blash.json new file mode 100644 index 0000000..42dd421 --- /dev/null +++ b/blash.json @@ -0,0 +1,324 @@ +{ + "banner" : "blash version 0.1
" + + "Copyright (C) 2010 BlackLight <blacklight@autistici.org>" + + "
Licence GPLv3+: GNU GPL version 3 or later " + + "<" + + "http://gnu.org/licences/gpl.html>

" + + "This is free software; you are free to change and " + + "redistribuite it.
There is NO WARRANTY, to the " + + "extent permitted by law.
" + + "Type 'man blash' for help on usage and available commands

", + + "user" : "blacklight", + "machine" : "localhost", + "shellName" : "blash", + "basepath" : "/", + "promptText" : "[#{800}%n#{888}@#{800}%m#{888} %W] $ ", + "promptSequences" : [ + { + "sequence" : "%n", + "default_text" : "blacklight", + "text" : function () { + return shell.json.user; + }, + }, + { + "sequence" : "%m", + "default_text" : "localhost", + "text" : function () { + return shell.json.machine; + }, + }, + { + "sequence" : "%W", + "default_text" : "~", + "text" : function () { + return shell.json.basepath; + }, + } + ], + + "directories" : [ + { + "path" : "/", + "type" : "directory", + }, + { + "path" : "/home", + "type" : "directory", + }, + { + "path" : "/home/blacklight", + "type" : "directory", + }, + { + "path" : "/etc", + "type" : "directory", + }, + { + "path" : "/initrd", + "type" : "file", + "href" : "http://www.google.com", + }, + ], + + "commands" : [ + { + "name" : "pwd", + + "info" : { + "syntax" : "pwd", + "brief" : "Print name of current/working directory", + }, + + "action" : function ( arg ) { + if ( arg ) + { + if ( arg.length > 0 ) + { + return this.name + ": Too many arguments
"; + } else { + return shell.path + '
'; + } + } else { + return shell.path + '
'; + } + }, + }, + + { + "name" : "clear", + + "info" : { + "syntax" : "clear", + "brief" : "Clear the terminal screen", + }, + + "action" : function ( arg ) { + var out = ''; + + shell.window.innerHTML = + '' + + '' + + ''; + + return out; + }, + }, + + { + "name" : "man", + + "info" : { + "syntax" : "man <page>", + "brief" : "Display the manual page for the command 'page'", + }, + + "action" : function ( arg ) { + var out = ''; + + if ( arg == undefined || arg.length == 0 ) + { + return "What manual page do you want?
\n"; + } + + var cmd = shell.json.commands; + + if ( arg == 'blash' ) + { + var commands = new Array(); + out = 'Type "man <command>" for a more detailed help on these commands

'; + + for ( var i=0; i < cmd.length; i++ ) + { + commands.push ( cmd[i].info.syntax ); + } + + commands.sort(); + + for ( var i in commands ) + { + out += '' + commands[i] + '
'; + } + + return out; + } + + var found = false; + + for ( var i=0; i < cmd.length && !found; i++ ) + { + if ( arg == cmd[i].name ) + { + if ( cmd[i].info.syntax.length > 0 && cmd[i].info.brief.length > 0 ) + { + found = true; + + out = + '' + cmd[i].info.syntax + '

' + + '' + cmd[i].info.brief + '
'; + } + } + } + + if ( !found ) + { + return "No manual entry for " + arg + "
\n"; + } + + return out; + }, + }, + + { + "name" : "ls", + + "info" : { + "syntax" : "ls [path]", + "brief" : "List directory contents", + }, + + "action" : function ( arg ) + { + var dirs = new Array(); + var out = ''; + var exists = false; + + if ( !arg || arg.length == 0 ) + { + var re = new RegExp ( '^' + shell.path + '[^/]+$' ); + } else if ( arg && arg.length > 0 ) { + var re = null; + + if ( arg.match ( /^\// )) + { + re = new RegExp ( '^' + arg + '/[^/]+$' ); + } else { + re = new RegExp ( '^' + shell.path + + (( shell.path == '/' ) ? '' : '/' ) + + arg + '/[^/]+$' ); + } + } + + for ( var i=0; i < shell.json.directories.length; i++ ) + { + var dir = shell.json.directories[i]; + + if ( dir.path.match ( re )) + { + exists = true; + dir.path.match ( /\/([^\/]+)$/ ); + dirs.push ({ + "path" : RegExp.$1, + "type" : dir.type, + "href" : dir.href, + }); + } + } + + if ( dirs.length > 0 ) + { + var ordered = false; + + // Directories go first + while ( !ordered ) + { + ordered = true; + + for ( var i=0; i < dirs.length-1; i++ ) + { + for ( var j=i+1; j < dirs.length; j++ ) + { + if ( dirs[i].type == 'file' && dirs[j].type == 'directory' ) + { + var tmp = dirs[i]; + dirs[i] = dirs[j]; + dirs[j] = tmp; + ordered = false; + } + } + } + } + + ordered = false; + + // Sort the names + while ( !ordered ) + { + ordered = true; + + for ( var i=0; i < dirs.length-1; i++ ) + { + for ( var j=i+1; j < dirs.length; j++ ) + { + if ( dirs[i].type == dirs[j].type && dirs[i].path > dirs[j].path ) + { + var tmp = dirs[i]; + dirs[i] = dirs[j]; + dirs[j] = tmp; + ordered = false; + } + } + } + } + + for ( var i in dirs ) + { + if ( dirs[i] ) + { + if ( dirs[i].path.length > 0 ) + { + if ( dirs[i].type == 'directory' ) + { + out += '' + dirs[i].path + '/
'; + } else { + if ( dirs[i].href && dirs[i].href.length > 0 ) + { + out += '' + dirs[i].path + '*
'; + } else { + out += '' + dirs[i].path + '
'; + } + } + } + } + } + } + + if ( !exists ) + { + var re = null; + + if ( arg.match ( /^\// )) + { + re = new RegExp ( '^' + arg ); + } else { + re = new RegExp ( '^' + shell.path + + (( shell.path == '/' ) ? '' : '/' ) + arg ); + } + + for ( var i=0; i < shell.json.directories.length; i++ ) + { + var dir = shell.json.directories[i]; + + if ( dir.path.match ( re )) + { + exists = true; + break; + } + } + + if ( !exists ) + { + out = this.name + ": cannot access " + arg + + ": No such file or directory
"; + } else { + out = ''; + } + } + + return out; + } + } + ], +} + diff --git a/index.html b/index.html new file mode 100644 index 0000000..09b9bf4 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + Blash - An AJAX interactive shell emulator for web browsing + + + + + +
+ + + +
+ + +