diff --git a/blash.css b/blash.css index 11bb6dd..1100a9f 100644 --- a/blash.css +++ b/blash.css @@ -82,3 +82,50 @@ span.match font-weight : bold; } +div.editor_container +{ + width : 100%; + height : 100%; + overflow : hidden; +} + +textarea.editor_window +{ + border : 0px; + width : 100%; + height : 93%; + background-color : black; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; +} + +.editor_status +{ + width : 100%; + background-color : black; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; + margin-bottom : 0; +} + +.editor_head +{ + width : 100%; + background-color : #222; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; + margin-bottom : 0; +} + +input.editor_status_input +{ + background-color : black; + color : #888; + font-family : Terminus, courier, monospace, fixed; + font-size : 13px; + border : 0; +} + diff --git a/commands/cd.json b/commands/cd.json index 4dedda8..5a85489 100644 --- a/commands/cd.json +++ b/commands/cd.json @@ -10,33 +10,66 @@ { var out = ''; - if ( !arg || arg.length == 0 ) + if ( !arg || arg.length == 0 || arg == '~' ) { - return "Parameter expected
\n"; - } + shell.auto_prompt_focus = false; + shell.auto_prompt_refresh = false; + + var users_php = window.location.href; + users_php = users_php.replace ( /\/([a-zA-Z\.]+)$/, '/modules/users/users.php' ); + params = 'action=gethome'; - var found = false; - arg = shell.expandPath ( arg ); + var http = new XMLHttpRequest(); + http.open ( "POST", users_php, true ); + http.setRequestHeader ( "Content-type", "application/x-www-form-urlencoded" ); + http.setRequestHeader ( "Content-length", params.length ); + http.setRequestHeader ( "Connection", "close" ); - for ( var i=0; i < shell.files.length && !found; i++ ) - { - if ( shell.files[i].path == arg ) + http.onreadystatechange = function () { - found = true; - - if ( shell.files[i].type != 'directory' ) + if ( http.readyState == 4 && http.status == 200 ) { - return "cd: not a directory: " + arg + "
\n"; + if ( http.responseText.length > 0 ) + { + shell.home = http.responseText; + shell.path = shell.home; + } else { + shell.user = shell.json.user; + } + + shell.cmdOut.innerHTML = ''; + shell.auto_prompt_focus = true; + shell.auto_prompt_refresh = true; + shell.refreshPrompt ( false, false ); } } + + http.send ( params ); + } else { + var found = false; + arg = shell.expandPath ( arg ); + + for ( var i=0; i < shell.files.length && !found; i++ ) + { + if ( shell.files[i].path == arg ) + { + found = true; + + if ( shell.files[i].type != 'directory' ) + { + return "cd: not a directory: " + arg + "
\n"; + } + } + } + + if ( !found ) + { + return "cd: No such file or directory: " + arg + "
\n"; + } + + shell.path = arg; } - if ( !found ) - { - return "cd: no such file or directory: " + arg + "
\n"; - } - - shell.path = arg; return out; }, } diff --git a/commands/nano.json b/commands/nano.json new file mode 100644 index 0000000..cf01b75 --- /dev/null +++ b/commands/nano.json @@ -0,0 +1,373 @@ +{ + "name" : "nano", + + "info" : { + "syntax" : "nano <file name>", + "brief" : "Edit the content of a new or existing file", + }, + + "action" : function ( arg ) + { + var file = null; + var parent_dir = null; + var newfile = false; + + if ( !arg || arg.length == 0 ) + { + return "nano: Parameter expected
\n"; + } + + arg = shell.expandPath ( arg ); + + if (!( parent_dir = shell.getParentDirectory ( arg ))) + { + return "nano: Parent directory not found
\n"; + } + + if ( !( file = shell.getFile ( arg ))) + { + newfile = true; + } + + if ( !newfile ) + { + if ( file['type'] == 'directory' ) + { + arg = arg.replace ( //g, '>' ); + return "nano: Cannot edit '" + arg + "': Is a directory
\n"; + } + } + + var users_php = window.location.href; + users_php = users_php.replace ( /\/([a-zA-Z\.]+)$/, '/modules/users/users.php' ); + params = 'action=getperms&resource=' + + ( newfile ? escape ( parent_dir['path'] ) : escape ( arg )); + + var http = new XMLHttpRequest(); + http.open ( "POST", users_php, true ); + http.setRequestHeader ("Content-type", "application/x-www-form-urlencoded"); + http.setRequestHeader ("Content-length", params.length); + http.setRequestHeader ("Connection", "close"); + + http.onreadystatechange = function () + { + if ( http.readyState == 4 && http.status == 200 ) + { + if ( http.responseText.length > 0 ) + { + shell.perms = eval ( '(' + http.responseText + ')' ); + } + } + } + + http.send ( params ); + + fname = arg.replace ( //g, '>' ); + + shell.fname = arg; + shell.editorkeypressed = this.editorkeypressed; + shell.confirmkey = this.confirmkey; + shell.bufferSave = this.bufferSave; + + shell.default_editor_status = "[^X Exit] [^O WriteOut] [^W Where Is]"; + + if ( shell.perms ) + { + if ( shell.perms.write == false ) + { + shell.default_editor_status += ' [read-only]'; + } + } + + shell.default_editor_head = "" + + "
blash nanoFile: " + fname + "
"; + + shell.auto_prompt_focus = false; + shell.auto_prompt_refresh = false; + shell.window.removeChild ( shell.prompt ); + shell.window.removeChild ( shell.cmdOut ); + shell.window.innerHTML = ''; + + var container = document.createElement ( 'div' ); + container.setAttribute ( 'class', 'editor_container' ); + container.setAttribute ( 'id', 'editor_container' ); + shell.window.appendChild ( container ); + shell.editor_container = container; + + var head = document.createElement ( 'span' ); + head.setAttribute ( 'class', 'editor_head' ); + head.setAttribute ( 'id', 'editor_head' ); + head.innerHTML = shell.default_editor_head; + container.appendChild ( head ); + shell.editor_head = document.getElementById ( 'editor_head' ); + + var editor = document.createElement ( 'textarea' ); + editor.setAttribute ( 'class', 'editor_window' ); + editor.setAttribute ( 'id', 'editor_window' ); + editor.setAttribute ( 'onkeypress', 'shell.editorkeypressed ( event )' ); + editor.innerHTML = ''; + + has_content = false; + + if ( file ) + { + if ( file.content ) + { + if ( file.content.length > 0 ) + { + var content = file.content.replace ( //g, "\n" ); + content = content.replace ( '<', '<' ); + content = content.replace ( '>', '>' ); + editor.value = content; + has_content = true; + } + } + } + + if ( !has_content ) + { + editor.value = ''; + shell.originalContent = ''; + } else { + shell.originalContent = file.content; + } + + container.appendChild ( editor ); + editor.focus(); + shell.file_changed = false; + shell.editor_window = document.getElementById ( 'editor_window' ); + + var status = document.createElement ( 'span' ); + status.setAttribute ( 'class', 'editor_status' ); + status.setAttribute ( 'id', 'editor_status' ); + status.innerHTML = shell.default_editor_status; + + container.appendChild ( status ); + shell.editor_status = document.getElementById ( 'editor_status' ); + + return ''; + }, + + "editorkeypressed" : function ( e ) + { + var evt = ( window.event ) ? window.event : e; + var key = ( evt.charCode ) ? evt.charCode : evt.keyCode; + key = String.fromCharCode ( key ); + + if ( !shell.file_changed ) + { + if ( shell.originalContent != shell.editor_window.value ) + { + if ( shell.perms ) + { + if ( shell.perms.write == false ) + { + shell.default_editor_status += ' [read-only]'; + shell.editor_status.innerHTML += ' [read-only]'; + } + } + + shell.editor_status.innerHTML += ' [modified]'; + shell.file_changed = true; + } + } + + if (( key == 'x' || key == 'X' ) && evt.ctrlKey ) + { + evt.preventDefault(); + + if ( shell.file_changed ) + { + var can_write = false; + + if ( shell.perms ) + { + if ( shell.perms.write == true ) + { + can_write = true; + } + } + + if ( can_write ) + { + shell.editor_status.innerHTML = 'Save modified buffer (ANSWERING "No" WILL DESTROY CHANGES) ? ' + + '(Yes/No/Cancel) '; + } else { + shell.editor_status.innerHTML = 'You modified a read-only file. If you exit, the changes will ' + + 'be lost. Are you sure you cant to exit? (Yes/No) '; + } + + shell.editor_status.innerHTML += ''; + + document.getElementById ( 'editor_status_input' ).focus(); + } else { + shell.auto_prompt_focus = true; + shell.auto_prompt_refresh = true; + shell.refreshPrompt ( true ); + } + } else if (( key == 'o' || key == 'O' ) && evt.ctrlKey ) { + evt.preventDefault(); + + if ( shell.file_changed ) + { + var can_write = false; + + if ( shell.perms ) + { + if ( shell.perms.write == true ) + { + can_write = true; + } + } + + if ( can_write ) + { + shell.bufferSave(); + shell.file_changed = false; + shell.editor_status.innerHTML = shell.default_editor_status; + } + } + } + }, + + "confirmkey" : function ( e ) + { + var can_write = false; + var evt = ( window.event ) ? window.event : e; + var key = ( evt.charCode ) ? evt.charCode : evt.keyCode; + key = String.fromCharCode ( key ); + + if ( shell.perms ) + { + if ( shell.perms.write == true ) + { + can_write = true; + } + } + + if ( can_write ) + { + switch ( key ) + { + case 'c': + case 'C': + evt.preventDefault(); + shell.editor_status.innerHTML = shell.default_editor_status + ' [modified]'; + shell.editor_window.focus(); + break; + + case 'y': + case 'Y': + shell.bufferSave(); + + case 'n': + case 'N': + evt.preventDefault(); + shell.auto_prompt_focus = true; + shell.auto_prompt_refresh = true; + shell.refreshPrompt ( true ); + break; + + default : + evt.preventDefault(); + document.getElementById ( 'editor_status_input' ).value = ''; + break; + } + } else { + switch ( key ) + { + case 'n': + case 'N': + evt.preventDefault(); + shell.editor_status.innerHTML = shell.default_editor_status + ' [modified]'; + shell.editor_window.focus(); + break; + + case 'y': + case 'Y': + evt.preventDefault(); + shell.auto_prompt_focus = true; + shell.auto_prompt_refresh = true; + shell.refreshPrompt ( true ); + break; + + default : + evt.preventDefault(); + document.getElementById ( 'editor_status_input' ).value = ''; + break; + } + } + }, + + "bufferSave" : function () + { + var users_php = window.location.href; + users_php = users_php.replace ( /\/([a-zA-Z\.]+)$/, '/modules/users/users.php' ); + params = 'action=set_content&file=' + escape ( shell.fname ) + '&content=' + escape ( document.getElementById ( 'editor_window' ).value ); + + var http = new XMLHttpRequest(); + http.open ( "POST", users_php, true ); + http.setRequestHeader ("Content-type", "application/x-www-form-urlencoded"); + http.setRequestHeader ("Content-length", params.length); + http.setRequestHeader ("Connection", "close"); + + http.onreadystatechange = function () + { + if ( http.readyState == 4 && http.status == 200 ) + { + if ( http.responseText.length > 0 ) + { + shell.editor_status.innerHTML = http.responseText; + setTimeout ( 'shell.editor_status.innerHTML = shell.default_editor_status', 5000 ); + + var files_config = window.location.href; + files_config = files_config.replace ( /\/([a-zA-Z\.]+)$/, '/modules/users/files.php' ); + + var http2 = new XMLHttpRequest(); + http2.open ( "GET", files_config, true ); + + http2.onreadystatechange = function () + { + if ( http2.readyState == 4 && http2.status == 200 ) + { + shell.files = eval ( '(' + http2.responseText + ')' ); + + // Remove duplicates + var tmp = new Array(); + + for ( var i in shell.files ) + { + var contains = false; + + for ( var j=0; j < tmp.length && !contains; j++ ) + { + if ( shell.files[i].path == tmp[j].path ) + { + contains = true; + } + } + + if ( !contains ) + { + tmp.push ( shell.files[i] ); + } + } + + shell.files = tmp; + } + } + + http2.send ( null ); + } + } + } + + http.send ( params ); + shell.originalContent = shell.editor_window.value; + shell.file_changed = false; + } +} + diff --git a/modules/users/user_utils.php b/modules/users/user_utils.php index 9fef7ea..e088ec3 100644 --- a/modules/users/user_utils.php +++ b/modules/users/user_utils.php @@ -305,6 +305,94 @@ function __json_encode( $data ) { return $json; } +function set_content ( $file, $content ) +{ + $perms = json_decode ( getPerms ( $file ), true ); + $can_write = true; + + if ( !$perms['write'] ) + { + $can_write = false; + + if ( $perms['message'] ) + { + if ( !strcasecmp ( $perms['message'], "Resource not found" )) + { + $parent = preg_replace ( "@/[^/]+$@", '', $file ); + $perms = json_decode ( getPerms ( $parent ), true ); + + if ( !$perms['write'] ) + { + if ( $perms['message'] ) + { + if ( !strcasecmp ( $perms['message'], "Resource not found" )) + { + return "Cannot save the file: Parent directory not found"; + } else { + return $perms['message']; + } + } else { + return "Cannot write to the file: Permission denied"; + } + } else { + $can_write = true; + } + } else { + return $perms['message']; + } + } else { + return "Cannot write to the file: Permission denied"; + } + } + + $resp = __touch ( $file, null ); + include "../../system/files_json.php"; + + if ( !$files_json || strlen ( $files_json ) == 0 ) + { + return 'Error: Empty JSON file container'; + } + + $json = json_decode ( $files_json, true ); + + if ( !$json ) + { + return 'Error: Empty JSON file container'; + } + + for ( $i=0; $i < count ( $json ); $i++ ) + { + $path = $json[$i]['path']; + + if ( !$path || strlen ( $path ) == 0 ) + { + continue; + } + + if ( $path == $file ) + { + $content = str_replace ( '<', '<', $content ); + $content = str_replace ( '>', '>', $content ); + $content = str_replace ( "\'", "'", $content ); + $content = str_replace ( '"', "'", $content ); + $content = str_replace ( '\\', '', $content ); + $content = str_replace ( "\r", '', $content ); + $content = str_replace ( "\n", '
', $content ); + + $json[$i]['content'] = $content; + + if ( !( $fp = fopen ( "../../system/files_json.php", "w" ))) + { + return "Unable to write on directories file\n"; + } + + fwrite ( $fp, ""); + fclose ( $fp ); + return "File successfully saved"; + } + } +} + function __touch ( $file, $own_perms ) { include "../../system/files_json.php"; diff --git a/modules/users/users.php b/modules/users/users.php index 0e0c464..be6bcee 100644 --- a/modules/users/users.php +++ b/modules/users/users.php @@ -241,6 +241,18 @@ switch ( $action ) print __rm ( $file ); break; + case 'set_content': + $file = $_REQUEST['file']; + $content = $_REQUEST['content']; + + if ( !( $file && $content )) + { + return false; + } + + print set_content ( $file, $content ); + break; + default : print "Unallowed action\n"; break; diff --git a/system/blash.js b/system/blash.js index c5fdfda..c4158c9 100644 --- a/system/blash.js +++ b/system/blash.js @@ -250,6 +250,8 @@ function blash () if ( key == 68 && evt.ctrlKey ) { + evt.preventDefault(); + /* CTRL-d -> logout */ for ( i=0; i < this.commands.length; i++ ) { @@ -312,6 +314,7 @@ function blash () return false; } else if ( key == 76 && evt.ctrlKey ) { // CTRL-l clears the screen + evt.preventDefault(); this.refreshPrompt ( true, false ); return false; } else if ( key == 13 || key == 10 || ( key == 67 && evt.ctrlKey )) { @@ -435,6 +438,7 @@ function blash () this.prompt.focus(); } else if ( key == 9 ) { this.prompt.focus(); + evt.preventDefault(); if ( this.prompt.value.match ( /\s(.*)$/ )) { @@ -613,13 +617,22 @@ function blash () */ this.refreshPrompt = function ( clearTerm, clearOut ) { - var value = this.prompt.value; - var out = this.cmdOut.innerHTML; + var value = ""; + var out = ""; var text = ( this.json.promptText ) ? this.json.promptText : "[%n@%m %W] $ "; text = this.unescapePrompt ( text, this.json.promptSequences ); - this.window.removeChild ( this.prompt ); - this.window.removeChild ( this.cmdOut ); + if (( this.prompt = document.getElementById ( "promptText" ))) + { + value = this.prompt.innerHTML; + this.window.removeChild ( this.prompt ); + } + + if (( this.cmdOut = document.getElementById ( "blashCmdOut" ))) + { + out = this.cmdOut.innerHTML; + this.window.removeChild ( this.cmdOut ); + } if ( clearTerm ) { @@ -630,7 +643,7 @@ function blash () { var outDiv = document.createElement ( 'span' ); outDiv.innerHTML = ((value.length > 0) ? value : '') + - '
' + ((out.length > 0) ? (out + '
') : '') + text; + '
' + ((out.length > 0) ? (out + '
') : '') + text + (( shell.__first_cmd ) ? '
' : '' ); this.window.appendChild ( outDiv ); } @@ -737,5 +750,70 @@ function blash () return matches; } + + /** + * \brief Check if a file or directory exists + * \return The object reference to the file or directory if it exists, false otherwise + */ + this.getFile = function ( arg ) + { + if ( !arg || arg.length == 0 ) + { + return false; + } + + arg = this.expandPath ( arg ); + + for ( var i=0; i < this.files.length; i++ ) + { + if ( this.files[i].path == arg ) + { + return this.files[i]; + } + } + + return false; + } + + /** + * \brief Check if the parent directory of a file exists + * \return The object reference to the parent directory, if it exists and it is actually a directory, false otherwise + */ + this.getParentDirectory = function ( arg ) + { + if ( !arg || arg.length == 0 ) + { + return false; + } + + arg = this.expandPath ( arg ); + + if ( arg.match ( /(\/[^\/]+)$/ )) + { + arg = arg.replace ( RegExp.$1, '' ); + } else { + arg = '/'; + } + + if ( arg.length == 0 ) + { + arg = '/'; + } + + for ( var i=0; i < this.files.length; i++ ) + { + if ( this.files[i].path == arg ) + { + if ( this.files[i].type == 'directory' ) + { + return this.files[i]; + } else { + return false; + } + } + } + + return false; + } } diff --git a/system/blash.json b/system/blash.json index 0178c4c..8bcda31 100644 --- a/system/blash.json +++ b/system/blash.json @@ -64,6 +64,7 @@ "ls", "man", "mkdir", + "nano", "passwd", "pwd", "rm", diff --git a/system/files_json.php b/system/files_json.php index b7b4026..2f5cadd 100644 --- a/system/files_json.php +++ b/system/files_json.php @@ -32,6 +32,7 @@ $files_json = <<lol lol asd 'lol' <asd> 'asd'"} ] JSON;