├── .gitignore ├── serve.sh ├── src ├── shell-motd.txt ├── login.html ├── set-password.php ├── util.php ├── commands.php ├── editor.css ├── index.php └── editor.js ├── package.json ├── LICENSE ├── README.md ├── Gruntfile.js └── dist └── editor.php /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /serve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | php -S localhost:8080 -t src/ 4 | -------------------------------------------------------------------------------- /src/shell-motd.txt: -------------------------------------------------------------------------------- 1 | Common shell commands: 2 | du -hd1|sort -h 3 | fdupes -rq . 4 | ps -eo pcpu,time,pid,args|tail -n +2|sort -nrk1 5 | iotop -obn1 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editor", 3 | "version": "1.0.1-master", 4 | "author": "Simon Thorpe", 5 | "description": "A quick and dirty PHP file manager", 6 | "engines": { 7 | "node": ">= 0.10.0" 8 | }, 9 | "devDependencies": { 10 | "grunt": "~0.4.2", 11 | "grunt-contrib-clean": "~1.0.0", 12 | "grunt-prettify": "~0.4.0", 13 | "grunt-esformatter": "~1.1.0", 14 | "grunt-contrib-jshint": "~1.0.0", 15 | "grunt-contrib-concat": "~1.0.0", 16 | "grunt-contrib-uglify": "~0.11.1", 17 | "grunt-contrib-cssmin": "~0.14.0", 18 | "grunt-replace": "~0.11.0", 19 | "grunt-shell": "~1.2.1", 20 | "grunt-jscs": "~2.7.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Simon Thorpe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Editor - Login 6 | 7 | 17 | 18 | 35 | 36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is a single PHP file that can be uploaded to a webserver. It will then allow browsing and editing of files using an HTML5 editor (no browser plugins required). 2 | 3 | The purpose of this project is to allow code editing/deleting/uploading on shared web space where FTP or other means of editing is unavailable or not practical (such as a Chromebook). 4 | 5 | #### Advantages over FTP 6 | * No IDE software needs to be installed 7 | * No complex firewall rules 8 | * HTML5 only - perfect for Chrome OS 9 | 10 | #### Disadvantages over FTP 11 | * Limited functionality 12 | * Must upload php script to begin with 13 | 14 | ## Installation 15 | Upload the editor.php file (in the dist/ directory) to the root of your web space then point your browser to http://yoursite.com/editor.php and login to edit files. Default password is 'admin'. 16 | 17 | #### Install - via SSH 18 | ``` 19 | cd public_html 20 | wget https://raw.githubusercontent.com/simon-thorpe/editor/master/dist/editor.php 21 | ``` 22 | 23 | #### Install - via FTP 24 | * Upload editor.php to the root of your web directory 25 | 26 | #### Install - via CMS 27 | Some content management systems allow creating simple text files. All you need to do is create a file editor.php (or any name you like) then paste the contents of dist/editor.php into that file. 28 | 29 | ## Building from Source 30 | ``` 31 | git clone https://github.com/simon-thorpe/editor.git 32 | cd editor 33 | npm install 34 | # edit dev files in src dir 35 | grunt 36 | # built file is in dist dir 37 | ``` 38 | 39 | ## API 40 | Uploading a file to the server 41 | ``` 42 | curl yoursite.com/editor.php -s -H "Cookie: editor-auth=yourpass" -F p=/home/yoursite/public_html/calendar.json -F 'content= 2 | 3 | 4 | 5 | Code Editor - Set Password 6 | 14 | 15 | 25 | 26 | 35 | 36 | 37 |
38 |
39 |
40 |

41 | This is the first time you have logged in and no password has been set. Create a new password below. 42 |

43 | 44 | 45 |
46 | 52 | 53 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /src/util.php: -------------------------------------------------------------------------------- 1 | 0 && $dir!='/')$relativePath=substr($fullPath,strlen($dir)+1); 9 | else $relativePath=$fullPath; 10 | 11 | // Remove trailing '/' as output should be relative to $dir. 12 | if($relativePath[0]=='/') 13 | $relativePath=substr($relativePath,1); 14 | 15 | array_push($r,$relativePath); 16 | } 17 | return $r; 18 | } 19 | function scandir_recursive($dir,$includeDirs=TRUE,$_prefix=''){ 20 | $dir=rtrim($dir,'\\/'); 21 | $r=array(); 22 | foreach(scandir($dir)as$f){ 23 | if($f!=='.'&&$f!=='..') 24 | if(is_dir("$dir/$f")){ 25 | $r=array_merge($r,scandir_recursive("$dir/$f",$includeDirs,"$_prefix$f/")); 26 | if($includeDirs)$r[]=$_prefix.$f; 27 | }else $r[]=$_prefix.$f; 28 | } 29 | return $r; 30 | } 31 | function human_readable_filesize($v) 32 | { 33 | if($v>=1024*1024*1024) 34 | return (floor($v/(1024*1024*1024)*10)/10)." GB"; 35 | elseif($v>=1024*1024) 36 | return (floor($v/(1024*1024)*10)/10)." MB"; 37 | elseif($v>=1024) 38 | return (floor($v/1024*10)/10)." KB"; 39 | else 40 | return $v." bytes"; 41 | } 42 | function human_readable_timespan($v) 43 | { 44 | if($v>=60*60*24) 45 | return (floor($v/(60*60*24)*10)/10)." days"; 46 | elseif($v>=60*60) 47 | return (floor($v/(60*60)*10)/10)." hours"; 48 | elseif($v>=60) 49 | return floor($v/60)." min"; 50 | else 51 | return $v." sec"; 52 | } 53 | function get_dir_count($dir,$du=false){ 54 | if(is_readable($dir)){ 55 | if($du==TRUE){ // Recursively calculate the size of the directory's descendant items. 56 | $output=shell_exec('/usr/bin/find '.escapeshellarg($dir).' |wc -l'); 57 | return intval($output)-1; 58 | } 59 | else{ 60 | // Cound the items within the directory. 61 | return count(scandir($dir))-2; // -2 to exclude the "." and ".." 62 | } 63 | } 64 | else 65 | return null; 66 | } 67 | function urlencodelite($s){ 68 | $s=rawurlencode($s); 69 | $s=str_replace('%2F','/',$s); 70 | $s=str_replace(' ','%20',$s); 71 | return $s; 72 | } 73 | ?> -------------------------------------------------------------------------------- /src/commands.php: -------------------------------------------------------------------------------- 1 | false,"message"=>"File already exists.")); 39 | else{ 40 | if($type==='dir'){ 41 | $r=mkdir($PATH); 42 | } 43 | elseif($type==='file'){ 44 | $r=file_put_contents($PATH,''); 45 | } 46 | //echo '{"success":'.($r!==false?'true':'false').'}'; 47 | echo json_encode(array("success"=>$r!==false,"message"=>"Error writing ".$PATH)); 48 | } 49 | exit; 50 | } 51 | elseif(isset($_GET["d"])){ 52 | // http://php.net/manual/en/function.readfile.php 53 | header('Content-Description: File Transfer'); 54 | $fi=finfo_open(FILEINFO_MIME); 55 | header('Content-Type: '.finfo_file($fi,$PATH)); 56 | finfo_close($fi); 57 | header('Content-Disposition: inline; filename='.basename($PATH)); 58 | header('Content-Transfer-Encoding: binary'); 59 | header('Cache-Control: private, max-age=0, must-revalidate'); 60 | header('Content-Length: '.filesize($PATH)); 61 | ob_clean(); 62 | flush(); 63 | readfile($PATH); 64 | exit; 65 | } 66 | elseif(isset($_FILES['file'])){ 67 | $dest=$PATH.'/'.$_FILES['file']['name']; 68 | $tempFile=$_FILES['file']['tmp_name']; 69 | header('Content-Type: application/json'); 70 | echo json_encode(array("success"=>move_uploaded_file($_FILES['file']['tmp_name'],$dest)===true)); 71 | exit; 72 | } 73 | ?> -------------------------------------------------------------------------------- /src/editor.css: -------------------------------------------------------------------------------- 1 | body{font-family:sans-serif;font-size:11px;margin:0;} 2 | a{text-decoration:none;} 3 | input[type="text"]{border:1px solid #444;} 4 | 5 | header nav a{float:left;padding:6px 7px;color:#444;} 6 | header nav a:hover{background-color:#aaa;} 7 | header nav:after{content:"";clear:both;display:table;} 8 | header nav .active{font-weight:bold;} 9 | 10 | form{display:none;border-top:1px solid #444;padding:10px;} 11 | form .fieldRow{margin-bottom:5px;} 12 | pre{margin:0 0 10px;} 13 | input[readonly]{background-color:#ddd;opacity:0.3;} 14 | 15 | #list{border-bottom:1px solid #444;font-weight:bold;} 16 | #list>div{width:100%;border-top:1px solid #444;display:table;} 17 | #list>div>*{display:table-cell;padding:2px 0 2px 3px;} 18 | #list>div>a .indent{margin-left:10px;} 19 | #list>div.bad{text-decoration:line-through;} 20 | #list .cut{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/cut.png');} 21 | #list .copy{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/page_copy.png');} 22 | #list .paste{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/page_paste.png');} 23 | #list .dir .paste{display:none;} 24 | #list .remove-clip{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/delete.png');} 25 | #list .del{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/delete.png');} 26 | #list .del:not([onclick]){display:none;} 27 | #list .direct{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/world_go.png');} 28 | #list .direct[href=""]{display:none;} 29 | #list .dl{width:16px;min-width:16px;background-size:100%;background-position:50% 50%;background-repeat:no-repeat;background-color:#fff;background-image:url('//cdnjs.cloudflare.com/ajax/libs/fatcow-icons/20130425/FatCow_Icons32x32/inbox_download.png');} 30 | #list>div:nth-of-type(odd){background-color:#dfd;} 31 | #list>div:hover{background-color:#ddd;} 32 | #list>div:nth-of-type(odd):hover{background-color:#ddd;} 33 | #list .seg:hover{text-decoration:underline;color:#444 !important;} 34 | #list .seg[href=""]:hover{text-decoration:none;color:#ccc !important;} 35 | #list .dir{cursor:pointer;} 36 | #list .file{cursor:pointer;} 37 | #list .filepath .slash{color:#bbb;} 38 | #list .dir .filepath,#list .dir .seg{color:#bb0;} 39 | #list .file .filepath,#list .file .seg{color:#1b0;} 40 | #list .file .seg.d{color:#bb0;} 41 | #list .seg[href=""]{color:#ccc;cursor:default;} /* File editing disabled - too large to edit */ 42 | #list .size{color:#bbb;margin-left:5px;font-size:10px;width:100px;} 43 | #list .dir .size:after{content:" files";} 44 | #list .age:after{content:" old";} 45 | #list .age{color:#bbb;font-size:10px;width:100px;} 46 | 47 | /*@media (max-width:979px){ 48 | body{font-size:12px;} 49 | #list>div>*{display:block;padding-bottom:0;} 50 | #list .size{float:left;height:16px;height:auto;padding:0 0 4px;} 51 | #list .age{float:left;height:16px;height:auto;padding:0 0 4px;} 52 | #list .direct,#list .dl,#list .del,#list .cut,#list .copy,#list .paste,#list .remove-clip{float:right;height:34px;width:34px;margin-top:-18px;padding:0;} 53 | #list .filepath{width:calc(100% - 132px);} 54 | }*/ 55 | 56 | @media (max-width:979px){ 57 | body{font-size:12px;} 58 | #list>div>*{display:block;padding-bottom:0;} 59 | #list .size{float:left;height:16px;height:auto;padding:0 0 4px;} 60 | #list .age{float:left;height:16px;height:auto;padding:0 0 4px;} 61 | #list .direct,#list .dl,#list .del,#list .cut,#list .copy,#list .paste,#list .remove-clip{float:right;height:17px;width:17px;padding:0;} 62 | } 63 | 64 | button{border:none;margin:0;font-size:10px;} 65 | #editor{margin-top:15px;position:absolute;top:0;bottom:0;left:0;right:0;} 66 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | var pkg=grunt.file.readJSON('package.json'); 5 | 6 | // Project configuration. 7 | grunt.initConfig({ 8 | // Metadata. 9 | pkg: pkg, 10 | trimPhpTags: function(s){s=s.substring(6);return s.substring(0,s.length-3);}, 11 | // Task configuration. 12 | clean: { 13 | pre: ['build/','dist/','src/editor.config.php'], 14 | post: ['build/'] 15 | }, 16 | prettify: { 17 | options: { 18 | indent: 1, 19 | indent_char: ' ', 20 | unformatted: [ 21 | "noscript" 22 | ] 23 | }, 24 | all: { 25 | expand: true, 26 | cwd: 'src/', 27 | src: ['*.html', 'set-password.php'], 28 | dest: 'src/' 29 | } 30 | }, 31 | jscs: { 32 | src: 'src/**/*.js', 33 | options: { 34 | "preset": "google", 35 | "maximumLineLength": 1000, 36 | "disallowMultipleVarDecl": false 37 | } 38 | }, 39 | esformatter: { 40 | src: 'src/**/*.js' 41 | }, 42 | concat: { 43 | options: { 44 | stripBanners: true 45 | }, 46 | dist: { 47 | src: ['src/<%= pkg.name %>.js'], 48 | dest: 'build/<%= pkg.name %>.min.js' 49 | } 50 | }, 51 | uglify: { 52 | options: { 53 | }, 54 | dist: { 55 | src: '<%= concat.dist.dest %>', 56 | dest: 'build/<%= pkg.name %>.min.js' 57 | } 58 | }, 59 | jshint: { 60 | options: { 61 | curly: true, 62 | eqeqeq: true, 63 | immed: true, 64 | latedef: true, 65 | newcap: true, 66 | noarg: true, 67 | sub: true, 68 | undef: true, 69 | unused: true, 70 | boss: true, 71 | eqnull: true, 72 | browser: true, 73 | devel: true, // supress alert() warnings 74 | globals: { 75 | jQuery: true, // supress jQuery undefined warnings 76 | ace: true 77 | } 78 | }, 79 | gruntfile: { 80 | src: 'Gruntfile.js' 81 | }, 82 | all: { 83 | src: ['src/**/*.js'] 84 | } 85 | }, 86 | cssmin: { 87 | minify: { 88 | expand: true, 89 | cwd: 'src/', 90 | src: ['*.css', '!*.min.css'], 91 | dest: 'build/', 92 | ext: '.min.css' 93 | } 94 | }, 95 | replace: { 96 | dist: { 97 | options: { 98 | patterns: [ 99 | // Use functions to return value for replacement instead of "replacement: '<%= grunt.file.read("src/shell-motd.txt") %>'" otherwise regex will clash with content inside the include target. 100 | { 101 | match: 'version', 102 | replacement: '<%= pkg.version %>' 103 | }, 104 | { 105 | match: 'buildDate', 106 | replacement: new Date().toISOString() 107 | }, 108 | { 109 | match: /require\('commands.php'\);/, 110 | replacement: function(){ 111 | var s=grunt.file.read("src/commands.php"); 112 | s=s.substring(6); // trim leading "" 114 | } 115 | }, 116 | { 117 | match: /require\('set-password.php'\);/, 118 | replacement: function(){return '?>'+grunt.file.read("src/set-password.php")+''+grunt.file.read("src/login.html")+'/, 126 | replacement: function(){return grunt.file.read("src/shell-motd.txt");} 127 | }, 128 | { 129 | match: /require\('util.php'\);/, 130 | replacement: function(){ 131 | var s=grunt.file.read("src/util.php"); 132 | s=s.substring(6); // trim leading "" 134 | } 135 | }, 136 | 137 | { // replace external css ref 138 | match: //, 139 | //replacement: function(){return '';} 140 | replacement: function(){return '';} 141 | }, 142 | { // with php handler 143 | match: /\/\/ @@css/, 144 | replacement: function(){ 145 | var r = "if(isset($_GET['css'])){"; 146 | r += "header('Content-Type: text/css');\n"; 147 | r += "header('Cache-Control: public, maxage=31536000');\n"; // 1 year cache 148 | r += "?>"+grunt.file.read("build/editor.min.css")+"<\/script>/, 157 | //replacement: function(){return '';} 158 | replacement: function(){return '';} 159 | }, 160 | { // with php handler 161 | match: /\/\/ @@js/, 162 | replacement: function(){ 163 | var r = "if(isset($_GET['js'])){"; 164 | r += "header('Content-Type: application/javascript');\n"; 165 | r += "header('Cache-Control: public, maxage=31536000');\n"; // 1 year cache 166 | r += "?>"+grunt.file.read("build/editor.min.js")+" 14 | &1'); 32 | exit; 33 | } 34 | // @@css 35 | // @@js 36 | require('util.php'); 37 | $PATH=isset($_REQUEST["p"])?$_REQUEST["p"]:''; 38 | if($PATH==='' && $_SERVER['REQUEST_METHOD']==='GET'){if(!$DEFAULT_DIR)$DEFAULT_DIR=realpath('.');header('Location: ?p='.urlencodelite($DEFAULT_DIR));exit;} 39 | 40 | header('Cache-Control: no-store'); // To disable caching on browser back button. 41 | 42 | if($ALLOW_SHELL && isset($_POST["ajaxShell"])){ 43 | $COMMAND=$_POST["ajaxShell"]; 44 | $TEMP_DIR_WITH_TRAILING_SLASH='/tmp/'; 45 | if(isset($_SERVER['TEMP'])){ 46 | $TEMP_DIR_WITH_TRAILING_SLASH=$_SERVER['TEMP']; 47 | if($WINDOWS)$TEMP_DIR_WITH_TRAILING_SLASH.='\\'; 48 | else $TEMP_DIR_WITH_TRAILING_SLASH.='/'; 49 | } 50 | $STDOUT_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.STDOUT'; 51 | $RESULT_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.RESULT'; 52 | $LASTCMD_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.LASTCMD'; 53 | $LOCK_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.LOCK'; 54 | $tempExecuteFile=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.'.($WINDOWS?'cmd':'sh'); 55 | $WD=$PATH; 56 | if($WINDOWS)$WD=str_replace('/','\\',$WD); 57 | $lastCmd=null; 58 | if(file_exists($LASTCMD_FILE))$lastCmd=file_get_contents($LASTCMD_FILE); 59 | header('Content-Type: application/json'); 60 | 61 | if(file_exists($LOCK_FILE)){ 62 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 63 | echo json_encode(array("continue"=>TRUE,"output"=>$output,"lastCmd"=>$lastCmd,"debugLockFile"=>$LOCK_FILE)); 64 | } 65 | elseif(file_exists($STDOUT_FILE)){ 66 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 67 | $result=file_get_contents($RESULT_FILE); 68 | unlink($STDOUT_FILE); 69 | unlink($RESULT_FILE); 70 | echo json_encode(array("continue"=>FALSE,"output"=>$output,"lastCmd"=>$lastCmd,"result"=>$result)); 71 | } 72 | elseif($COMMAND){ 73 | $output=null; 74 | file_put_contents($LOCK_FILE,''); 75 | file_put_contents($LASTCMD_FILE,$COMMAND); 76 | if($WINDOWS){ // No async for windows yet 77 | file_put_contents($tempExecuteFile,"cd \"".$WD."\" || exit 1\n".$SHELL_PRE."\n".$COMMAND." >>".$STDOUT_FILE."\ndel ".$LASTCMD_FILE."\ndel ".$LOCK_FILE."\ndel ".$tempExecuteFile); 78 | //shell_exec('START /B CMD /C CALL \"'+$tempExecuteFile.'\"'); 79 | shell_exec($tempExecuteFile); 80 | //usleep(100000); // So we can see some output on the first round. 81 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 82 | } 83 | else{ 84 | file_put_contents($tempExecuteFile,"#!/bin/bash\ncd ".escapeshellarg($WD)." || exit 1\n".$SHELL_PRE."\n{ ".$COMMAND."; } 2>&1\nRESULT=$?\nrm ".$LASTCMD_FILE."\nrm ".$LOCK_FILE."\nrm ".$tempExecuteFile."\n[[ \$RESULT == 0 ]] && printf success >$RESULT_FILE || printf failure >$RESULT_FILE"); 85 | shell_exec('/bin/bash '.$tempExecuteFile.' >>'.$STDOUT_FILE.' &'); 86 | //usleep(100000); // So we can see some output on the first round. 87 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 88 | } 89 | echo json_encode(array("first"=>TRUE,"continue"=>TRUE,"output"=>$output 90 | ,"debug_tempExecuteFile"=>$tempExecuteFile,"debug_command"=>$COMMAND,"debug_stdout"=>$STDOUT_FILE 91 | )); 92 | } 93 | else{ 94 | // Nothing to do. 95 | echo json_encode(array("idle"=>TRUE)); 96 | } 97 | exit(); 98 | } 99 | 100 | require('commands.php'); 101 | 102 | $Recursive=FALSE; 103 | if(isset($_REQUEST["r"]))$Recursive=TRUE; 104 | $Grep=""; 105 | if(isset($_REQUEST["grep"]))$Grep=$_REQUEST["grep"]; 106 | $Find=""; 107 | if(isset($_REQUEST["find"]))$Find=$_REQUEST["find"]; 108 | $Locate=""; 109 | if(isset($_REQUEST["locate"]))$Locate=$_REQUEST["locate"]; 110 | $Title=substr($PATH,strrpos(str_replace('\\','/',$PATH),'/')+1); 111 | if($Title=='')$Title='Code Editor'; 112 | ?> 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | <?php echo $Title;?> 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
129 | 130 | 138 |
139 |
140 | Search:
141 |
142 | 143 | 144 | 145 |
146 |
> 147 | 148 | 149 |
150 | 151 |
&1',$errorCode); ?>
152 | 153 | 154 | 155 |
156 | 157 | > 158 |
159 | 160 |
161 |
162 | "; 177 | $c=file_get_contents($PATH); 178 | if(substr($c,0,3)==pack("CCC",0xef,0xbb,0xbf)) // Remove BOM 179 | $c=substr($c,3); 180 | $html.="
" . htmlentities($c,ENT_SUBSTITUTE) . "
"; 181 | return $html; 182 | } 183 | function getFiles(){ 184 | global $PATH,$Recursive,$Locate,$DIRS_AT_TOP; 185 | if($Locate) 186 | $r=getFilesUsingLocate($PATH,$Locate); 187 | elseif($Recursive) 188 | $r=scandir_recursive($PATH); 189 | else 190 | $r=scandir($PATH); 191 | sort($r,SORT_STRING|SORT_FLAG_CASE); 192 | 193 | if($DIRS_AT_TOP && !$Locate && !$Recursive){ 194 | $files=array(); 195 | $dirs=array(); 196 | foreach($r as $f) 197 | { 198 | if(is_dir($PATH.'/'.$f)) 199 | array_push($dirs,$f); 200 | else 201 | array_push($files,$f); 202 | } 203 | $r=array_merge($dirs,$files); 204 | } 205 | return $r; 206 | } 207 | function renderFileList() 208 | { 209 | global $PATH,$Grep,$Find,$Recursive; 210 | $editorPPath=realpath($_SERVER["DOCUMENT_ROOT"]); 211 | $files=getFiles(); 212 | $html=""; 213 | $html.="
"; 214 | if ($PATH != ""){ 215 | $PathEscaped=str_replace("'","\\'",$PATH); 216 | $delOnclick="onclick=\""."if(editor.del('$PathEscaped',null)===true){window.location=$('#list>.dir:first a:first').attr('href');}return(false)\""; 217 | if(get_dir_count($PATH)>0&&!is_link($PATH)) # Hide del button if dir not empty and not a symlink. 218 | $delOnclick=''; 219 | $up=substr($PATH,0, strrpos($PATH,'/')); 220 | if($up==='') 221 | $up='/'; 222 | elseif($Recursive) 223 | $up=$PATH; # Back out of search/recursive mode instead of up a level. 224 | $html.=""; 225 | } 226 | foreach($files as $filePath) 227 | { 228 | $rFile=$filePath; 229 | $aFile=($PATH=='/'?'':$PATH).'/'.$rFile; 230 | $pFile=realpath($aFile); 231 | if($rFile=='.'||$rFile=='..') 232 | continue; 233 | $isDir=is_dir($aFile); 234 | 235 | 236 | if($Find){ 237 | if(preg_match('/'.$Find.'/i',$rFile)!==1) 238 | continue; 239 | } 240 | if($Grep!=''){ 241 | if($isDir)continue; 242 | $fileContents=file_get_contents($aFile,false,null,0,5242880); // Limit to 5MB 243 | if(strpos(strtolower($fileContents),strtolower($Grep))===FALSE) 244 | continue; 245 | } 246 | 247 | if($isDir){ 248 | $size=get_dir_count($aFile); 249 | $friendlySize=$size; 250 | } 251 | else{ 252 | $size=filesize($aFile); 253 | $friendlySize=human_readable_filesize(filesize($aFile)); 254 | } 255 | $age=human_readable_timespan(time()-filemtime($aFile)); 256 | $direct=''; 257 | if(strpos($pFile,$editorPPath)===0){ 258 | $direct=urlencodelite(str_replace('\\','/',substr($pFile,strlen($editorPPath)))); 259 | if($direct=='')$direct='/'; 260 | } 261 | $aFileEscaped=str_replace("'","\\'",$aFile); 262 | if($isDir) 263 | $dlAnchor=''; 264 | else 265 | $dlAnchor=''; 266 | $delOnclick="onclick=\"editor.del('$aFileEscaped',this);return(false)\""; 267 | if($isDir&&$size!==0&&!is_link($aFile))$delOnclick=''; # Hide del button if dir not empty and not a symlink. 268 | 269 | $segAs=''; 270 | 271 | // Anchor each path segment. 272 | $segs=explode('/',$rFile); 273 | $segsCount=count($segs); 274 | $segAppend=''; 275 | for($i=0;$i<$segsCount;$i++){ 276 | $segAppend.='/'.$segs[$i]; 277 | if($i===$segsCount-1 && ($size>=1048576 || preg_match('/\.(mp3|aac|ogg|wav|mid|jpg|bmp|gif|png|webp|webm|mp4|mkv|m4v|avi|pdf|zip|rar|tar|gz|7z)$/i',$rFile)===1)) 278 | // Don't allow editing if file is over 1MB or is a media type. 279 | $href=$direct; 280 | else 281 | $href='?p='.urlencodelite(($PATH==='/'?'':$PATH).$segAppend); 282 | $segIsDir=$i!==$segsCount-1; 283 | $segAs.=' / '.$segs[$i].''; 284 | } 285 | $segAs=substr($segAs,28); // Trim leading " / " 286 | 287 | $pasteAnchor=''; 288 | if($isDir) 289 | $pasteAnchor=""; 290 | 291 | $html.='
'.$segAs."
$friendlySize$age$dlAnchor$pasteAnchor
"; 292 | } 293 | $html.="
"; 294 | return $html; 295 | } 296 | if(isset($BRANDING_FOOTER))echo $BRANDING_FOOTER; 297 | ?> 298 | 299 | $(function(){$(".shellButton").triggerHandler("click");});'; // If was postback then shell form is already open so need to trigger click to init form events ?> 300 | 301 | 302 | -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | var Path = '', 4 | editor = {}; 5 | try { 6 | if (window.location.search.search(new RegExp('[?&]p=([^&$]*)', 'i')) !== -1) { 7 | Path = RegExp.$1; 8 | } 9 | Path = decodeURIComponent(Path); 10 | } catch (e) { 11 | alert(e); 12 | alert('Path=' + Path); 13 | } 14 | window.editor = editor; 15 | editor.detectFileMode = function(filePath) { 16 | if (/^\/etc\/apache2\//.test(filePath)) { 17 | return 'apache_conf'; 18 | } else if (/Dockerfile$/.test(filePath)) { 19 | return 'dockerfile'; 20 | } 21 | var ext = filePath.substring(filePath.lastIndexOf('.') + 1); 22 | // https://github.com/ajaxorg/ace/tree/master/lib/ace/mode 23 | switch (ext) { 24 | case 'css': 25 | return 'css'; 26 | case 'js': 27 | return 'javascript'; 28 | case 'json': 29 | return 'json'; 30 | case 'asax': 31 | case 'ashx': 32 | case 'cs': 33 | return 'csharp'; 34 | case 'xml': 35 | return 'xml'; 36 | case 'phtml': 37 | case 'php': 38 | return 'php'; 39 | case 'config': 40 | return 'xml'; 41 | case 'as': 42 | return 'actionscript'; 43 | case 'bat': 44 | case 'cmd': 45 | return 'batchfile'; 46 | case 'c': 47 | case 'h': 48 | case 'hpp': 49 | case 'cpp': 50 | return 'c_cpp'; 51 | case 'coffee': 52 | return 'coffee'; 53 | case 'dart': 54 | return 'dart'; 55 | case 'diff': 56 | return 'diff'; 57 | case 'asp': 58 | case 'asa': 59 | case 'aspx': 60 | case 'ascx': 61 | case 'htm': 62 | case 'html': 63 | return 'html'; 64 | case 'ini': 65 | return 'ini'; 66 | case 'java': 67 | return 'java'; 68 | case 'jsp': 69 | return 'jsp'; 70 | case 'less': 71 | return 'less'; 72 | case 'lua': 73 | return 'lua'; 74 | case 'pl': 75 | return 'perl'; 76 | case 'ps': 77 | return 'powershell'; 78 | case 'py': 79 | return 'python'; 80 | case 'cgi': 81 | case 'sh': 82 | return 'sh'; 83 | case 'sql': 84 | return 'sql'; 85 | case 'svg': 86 | return 'svg'; 87 | case 'md': 88 | return 'markdown'; 89 | default: 90 | return ''; 91 | } 92 | }; 93 | editor.detectFileModeByContent = function(content) { 94 | if (content.indexOf('#!/bin/sh') === 0) { 95 | return 'sh'; 96 | } 97 | if (content.indexOf('#!/bin/bash') === 0) { 98 | return 'sh'; 99 | } 100 | return ''; 101 | }; 102 | editor.save = function(close) { 103 | jQuery.ajax({ 104 | url: '?', 105 | type: 'post', 106 | dataType: 'json', 107 | data: { 108 | content: editor.instance.getValue(), 109 | p: Path 110 | }, 111 | success: function() { 112 | if (close) { 113 | history.back(); 114 | } else { 115 | if (editor.isModified) { 116 | editor.isModified = false; 117 | document.title = document.title.substring(1); // remove leading '*' 118 | } 119 | } 120 | }, 121 | error: function(r) { 122 | alert(r.responseText); 123 | } 124 | }); 125 | }; 126 | editor.clip = { 127 | getItems: function() { 128 | var j = localStorage.clipValue; 129 | return j ? JSON.parse(j) : []; 130 | }, 131 | setItems: function(items) { 132 | localStorage.clipValue = JSON.stringify(items); 133 | }, 134 | add: function(path, type) { 135 | var i = this.getItems(); 136 | i.push({ 137 | path: path, 138 | type: type 139 | }); 140 | this.setItems(i); 141 | }, 142 | remove: function(o) { 143 | var path = o, 144 | item, 145 | items = editor.clip.getItems(); 146 | if (typeof (path) === 'object') { 147 | path = o.path; 148 | } 149 | item = items.filter(function(x) { 150 | return x.path === path; 151 | })[0]; 152 | if (!item) { 153 | return false; 154 | } 155 | items.splice(items.indexOf(item), 1); 156 | this.setItems(items); 157 | return true; 158 | } 159 | }; 160 | editor.del = function(aPath, trigger) { 161 | var success = false, 162 | div = $(trigger).parent('div'); 163 | if (div.length === 0) { 164 | console.error('DEBUG: Cannot find row in DOM.'); 165 | } 166 | $.ajax({ 167 | url: '?', 168 | method: 'post', 169 | data: { 170 | p: aPath, 171 | rm: 1 172 | }, 173 | async: false, 174 | success: function(r) { 175 | if (r.success === true) { 176 | div.remove(); 177 | success = true; 178 | } else { 179 | alert(r.message); 180 | } 181 | }, 182 | error: function(r) { 183 | alert(r.responseText); 184 | } 185 | }); 186 | return success; 187 | }; 188 | editor.cut = function(aPath) { 189 | editor.clip.add(aPath, 'cut'); 190 | console.log('Clipboard: ' + aPath); 191 | }; 192 | editor.copy = function(aPath) { 193 | editor.clip.add(aPath, 'copy'); 194 | console.log('Clipboard: ' + aPath); 195 | }; 196 | editor.paste = function(clipItemPath, dest) { 197 | var item, 198 | pasteAs, 199 | data; 200 | if (!clipItemPath) { 201 | clipItemPath = editor.clip.getItems()[0].path; 202 | } 203 | item = editor.clip.getItems().filter(function(x) { 204 | return x.path === clipItemPath; 205 | })[0]; 206 | pasteAs = prompt( 207 | item.type === 'copy' ? 'Copy to:' : 'Move to:', item.path.substring(1 + item.path.lastIndexOf('/'))); 208 | if (!pasteAs) { 209 | return; 210 | } 211 | dest += '/' + pasteAs; 212 | data = { 213 | source: item.path, 214 | dest: dest 215 | }; 216 | if (item.type === 'copy') { 217 | data.cp = 1; 218 | } else if (item.type === 'cut') { 219 | data.mv = 1; 220 | } 221 | if (dest) { 222 | $.ajax({ 223 | url: '?', 224 | method: 'post', 225 | data: data, 226 | success: function(r) { 227 | if (r.success === true) { 228 | editor.clip.remove(clipItemPath); 229 | window.location = window.location; 230 | } else { 231 | alert(r.message); 232 | } 233 | }, 234 | error: function(r) { 235 | alert(r.responseText); 236 | } 237 | }); 238 | } 239 | }; 240 | editor.instance = null; 241 | (function() { 242 | var clipItems, 243 | list, 244 | mode; 245 | $('#editor').each(function() { 246 | $('header.list').hide(); 247 | mode = editor.detectFileMode(Path); 248 | if (!mode) { 249 | mode = editor.detectFileModeByContent(document.getElementById('editor').textContent.trim()); 250 | } 251 | editor.instance = ace.edit('editor'); 252 | //editor.instance.setTheme('ace/theme/monokai'); 253 | if ($(this).get(0).hasAttribute('data-readonly')) { 254 | document.title += ' [readonly]'; 255 | editor.instance.setOptions({ 256 | readOnly: true, 257 | highlightActiveLine: false, 258 | highlightGutterLine: false 259 | }); 260 | } 261 | editor.instance.on('change', function() { 262 | if (!editor.isModified) { 263 | editor.isModified = true; 264 | document.title = '*' + document.title; 265 | } 266 | }); 267 | if (mode) { 268 | if (typeof (console) !== 'undefined') { 269 | console.log('Setting editor mode: ' + 'ace/mode/' + mode); 270 | } 271 | editor.instance.getSession().setMode('ace/mode/' + mode); 272 | } else { 273 | if (typeof (console) !== 'undefined') { 274 | console.log('Unsupported editor mode. All available modes here: https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict'); 275 | } 276 | } 277 | editor.instance.commands.addCommand({ 278 | name: 'Save', 279 | bindKey: { 280 | win: 'Ctrl-s', 281 | mac: 'Command-s' 282 | }, 283 | exec: function() { 284 | editor.save(false); 285 | } 286 | }); 287 | }); 288 | 289 | // Remove any hashes from URL. 290 | if (window.location.hash) { 291 | window.location = window.location.search; 292 | } 293 | 294 | // Append clipboard files to the list. 295 | clipItems = editor.clip.getItems(); 296 | list = $('#list'); 297 | clipItems.forEach(function(clipItem) { 298 | var a = $('').text(clipItem.path), 299 | pasteButton = $('').addClass('paste').attr('href', '#'), 300 | removeButton = $('').addClass('remove-clip').attr('href', '#'); 301 | list.append($('
').addClass('clip').append(a).append(pasteButton).append(removeButton)); 302 | pasteButton.click(function() { 303 | editor.paste(clipItem.path, Path); 304 | return false; 305 | }); 306 | removeButton.click(function() { 307 | if (editor.clip.remove(clipItem.path)) { 308 | $(this).parent().remove(); 309 | } 310 | return false; 311 | }); 312 | }); 313 | 314 | // Show the paste button for all directories if there is an item in the clipboard. 315 | if (clipItems.length > 0) { 316 | $('#list>div.dir>a.paste').css({ 317 | display: 'table-cell' 318 | }); 319 | } 320 | }()); 321 | 322 | // Keyboard shortcuts. 323 | $(document).keydown(function(e) { 324 | if (e.keyCode === 115) { // F4 325 | $('.shellButton').click(); 326 | } 327 | }); 328 | $(function() { 329 | $('.searchForm select').change(function() { 330 | if ($(this).val() === 'Locate Database') { 331 | $('.searchForm input[type=checkbox]').prop('checked', false).prop('disabled', true); 332 | } else { 333 | $('.searchForm input[type=checkbox]').prop('disabled', false); 334 | } 335 | }); 336 | $('.searchForm button').click(function() { 337 | var select = $('.searchForm select').val(), 338 | url = '?p=' + encodeURIComponent(Path).replace(/%2F/g, '/'); 339 | if ($('.searchForm input[type=checkbox]').prop('checked')) { 340 | url += '&r='; 341 | } 342 | if (select === 'Filenames' || select === 'All') { 343 | url += '&find=' + encodeURIComponent($('.searchForm input[type=text]').val()).replace(/%2F/g, '/'); 344 | } 345 | if (select === 'Content (All Files)' || select === 'All') { 346 | url += '&grep=' + encodeURIComponent($('.searchForm input[type=text]').val()).replace(/%2F/g, '/'); 347 | } 348 | if (select === 'Content (Code Only)') { 349 | url += '&find=^[^.]*$|\\.(php|js|json|..?ss|p?html?|as..?|cs|vb|rb|py|txt|md|xml|xslt?|config)$&grep=' + encodeURIComponent($('.searchForm input[type=text]').val()).replace(/%2F/g, '/'); 350 | } 351 | if (select === 'Locate Database') { 352 | url += '&locate=' + encodeURIComponent($('.searchForm input[type=text]').val().replace(/[ ]+/g, '.*')).replace(/%2F/g, '/'); 353 | } 354 | window.location = url; 355 | }); 356 | $('.newButton').click(function() { 357 | var v = prompt('New file (end with / for dir):', 'new.txt'), 358 | type = 'file'; 359 | //if(v.endsWith('/')){ 360 | if (v.substring(v.length - 1) === '/') { 361 | type = 'dir'; 362 | v = v.substring(0, v.length - 1); 363 | } 364 | if (v) { 365 | $.ajax({ 366 | url: '?', 367 | method: 'post', 368 | data: { 369 | new: 1, 370 | p: Path + '/' + v, 371 | type: type 372 | }, 373 | dataType: 'json', 374 | success: function(r) { 375 | if (r.success === true) { 376 | window.location = '?p=' + encodeURIComponent(Path + '/' + v).replace(/%2F/g, '/'); 377 | } else { 378 | alert(r.message); 379 | } 380 | }, 381 | error: function(r) { 382 | alert(r.responseText); 383 | } 384 | }); 385 | } 386 | }); 387 | $('.searchButton').click(function() { 388 | $(this).addClass('active'); 389 | $('.searchForm').show().find('input[type=text]:first').select(); 390 | }); 391 | $('.shellButton').click(function() { 392 | $(this).addClass('active'); 393 | var input = $('.shellForm').show().find('input[type=text]:first').select(), 394 | first = true, 395 | continuingLastSession = false, 396 | blinkOn = true, 397 | callNext; 398 | callNext = function(cmd) { 399 | if (typeof (cmd) === 'undefined') { 400 | cmd = null; 401 | } 402 | $.ajax({ 403 | url: '?', 404 | type: 'post', 405 | dataType: 'json', 406 | data: { 407 | 'ajaxShell': cmd, 408 | p: Path 409 | }, 410 | success: function(r) { 411 | if (r.lastCmd) { 412 | input.val(r.lastCmd).prop('readonly', true); 413 | } 414 | if (r.idle === true) { 415 | input.prop('readonly', false); 416 | return; 417 | } 418 | if (first === true && r.first !== true) { 419 | continuingLastSession = true; 420 | $('.shellForm input#shellFormBackground').prop('checked', true); 421 | } 422 | first = false; 423 | blinkOn = !blinkOn; 424 | $('#shellOutput').html((continuingLastSession ? 'Continuing last session...\n\n' : '') + r.output + (r.continue === true ? (blinkOn ? '_' : ' ') : '')); 425 | if (r.continue === true) { 426 | setTimeout(callNext, 500); 427 | } else { // Command finished 428 | if (r.result === 'failure') { 429 | $('.shellForm').css({ 430 | backgroundColor: '#f44' 431 | }); 432 | alert('Last command failed.'); 433 | } else if (r.result === 'success') { 434 | $('.shellForm').css({ 435 | backgroundColor: '#6f6' 436 | }); 437 | } else { 438 | $('.shellForm').css({ 439 | backgroundColor: '#bbb' 440 | }); 441 | alert('Last command had no result.'); 442 | } 443 | window.onbeforeunload = null; 444 | input.prop('readonly', false).select(); 445 | } 446 | } 447 | }); 448 | }; 449 | callNext(); 450 | $('.shellForm').submit(function() { 451 | var shellHistory = JSON.parse(localStorage.shellHistory || '[]'); 452 | shellHistory.push(input.val()); 453 | localStorage.shellHistory = JSON.stringify(shellHistory); 454 | 455 | setTimeout(function() { 456 | window.onbeforeunload = function() { 457 | return 'You have a shell command running.'; 458 | }; 459 | }, 500); 460 | 461 | if (!$('.shellForm input#shellFormBackground').prop('checked')) { 462 | return true; // Normal form submit. 463 | } 464 | $('.shellForm').css({ 465 | backgroundColor: '#fff' 466 | }); // prepare background for ajax command where result will set red or green background 467 | callNext(input.val()); 468 | return false; 469 | }); 470 | }); 471 | $('#list .dir a[class!="seg"], #list .file a[class!="seg"]').click(function(e) { 472 | e.stopPropagation(); 473 | }); 474 | 475 | // Full row click. 476 | $('#list .dir, #list .file').click(function() { 477 | $(this).find('a.seg:last').get(0).click(); 478 | }); 479 | 480 | var buildShellHistoryDataList = function() { 481 | var datalist = $('#shellHistory'), 482 | shellHistory = JSON.parse(localStorage.shellHistory || '[]'), 483 | i, 484 | item; 485 | for (i = 0; i < shellHistory.length, item = shellHistory[i]; i += 1) { 486 | datalist.append($('").text(d.path),g=a("").addClass("paste").attr("href","#"),h=a("").addClass("remove-clip").attr("href","#");e.append(a("
").addClass("clip").append(f).append(g).append(h)),g.click(function(){return c.paste(d.path,b),!1}),h.click(function(){return c.clip.remove(d.path)&&a(this).parent().remove(),!1})}),d.length>0&&a("#list>div.dir>a.paste").css({display:"table-cell"})}(),a(document).keydown(function(b){115===b.keyCode&&a(".shellButton").click()}),a(function(){a(".searchForm select").change(function(){"Locate Database"===a(this).val()?a(".searchForm input[type=checkbox]").prop("checked",!1).prop("disabled",!0):a(".searchForm input[type=checkbox]").prop("disabled",!1)}),a(".searchForm button").click(function(){var c=a(".searchForm select").val(),d="?p="+encodeURIComponent(b).replace(/%2F/g,"/");a(".searchForm input[type=checkbox]").prop("checked")&&(d+="&r="),"Filenames"!==c&&"All"!==c||(d+="&find="+encodeURIComponent(a(".searchForm input[type=text]").val()).replace(/%2F/g,"/")),"Content (All Files)"!==c&&"All"!==c||(d+="&grep="+encodeURIComponent(a(".searchForm input[type=text]").val()).replace(/%2F/g,"/")),"Content (Code Only)"===c&&(d+="&find=^[^.]*$|\\.(php|js|json|..?ss|p?html?|as..?|cs|vb|rb|py|txt|md|xml|xslt?|config)$&grep="+encodeURIComponent(a(".searchForm input[type=text]").val()).replace(/%2F/g,"/")),"Locate Database"===c&&(d+="&locate="+encodeURIComponent(a(".searchForm input[type=text]").val().replace(/[ ]+/g,".*")).replace(/%2F/g,"/")),window.location=d}),a(".newButton").click(function(){var c=prompt("New file (end with / for dir):","new.txt"),d="file";"/"===c.substring(c.length-1)&&(d="dir",c=c.substring(0,c.length-1)),c&&a.ajax({url:"?",method:"post",data:{"new":1,p:b+"/"+c,type:d},dataType:"json",success:function(a){a.success===!0?window.location="?p="+encodeURIComponent(b+"/"+c).replace(/%2F/g,"/"):alert(a.message)},error:function(a){alert(a.responseText)}})}),a(".searchButton").click(function(){a(this).addClass("active"),a(".searchForm").show().find("input[type=text]:first").select()}),a(".shellButton").click(function(){a(this).addClass("active");var c,d=a(".shellForm").show().find("input[type=text]:first").select(),e=!0,f=!1,g=!0;c=function(h){"undefined"==typeof h&&(h=null),a.ajax({url:"?",type:"post",dataType:"json",data:{ajaxShell:h,p:b},success:function(b){return b.lastCmd&&d.val(b.lastCmd).prop("readonly",!0),b.idle===!0?void d.prop("readonly",!1):(e===!0&&b.first!==!0&&(f=!0,a(".shellForm input#shellFormBackground").prop("checked",!0)),e=!1,g=!g,a("#shellOutput").html((f?"Continuing last session...\n\n":"")+b.output+(b["continue"]===!0?g?"_":" ":"")),void(b["continue"]===!0?setTimeout(c,500):("failure"===b.result?(a(".shellForm").css({backgroundColor:"#f44"}),alert("Last command failed.")):"success"===b.result?a(".shellForm").css({backgroundColor:"#6f6"}):(a(".shellForm").css({backgroundColor:"#bbb"}),alert("Last command had no result.")),window.onbeforeunload=null,d.prop("readonly",!1).select())))}})},c(),a(".shellForm").submit(function(){var b=JSON.parse(localStorage.shellHistory||"[]");return b.push(d.val()),localStorage.shellHistory=JSON.stringify(b),setTimeout(function(){window.onbeforeunload=function(){return"You have a shell command running."}},500),a(".shellForm input#shellFormBackground").prop("checked")?(a(".shellForm").css({backgroundColor:"#fff"}),c(d.val()),!1):!0})}),a('#list .dir a[class!="seg"], #list .file a[class!="seg"]').click(function(a){a.stopPropagation()}),a("#list .dir, #list .file").click(function(){a(this).find("a.seg:last").get(0).click()});var c=function(){var b,c,d=a("#shellHistory"),e=JSON.parse(localStorage.shellHistory||"[]");for(b=0;b").attr("value",c))};c();var d=null,e=function(b){null===d&&(d=a('').text("Uploading ").append(a("")),a(document.body).append(d),d.get(0).showModal());var c=d.find("progress").get(0);c.value=b,100===b&&(alert("Upload Complete!"),window.location=window.location)};a(document.body).dropzone({url:"?p="+b,clickable:!1,previewsContainer:"body",totaluploadprogress:e}),a(".uploadButton").dropzone({url:"?p="+b,clickable:!0,previewsContainer:"body",totaluploadprogress:e})})}(jQuery);0 && $dir!='/')$relativePath=substr($fullPath,strlen($dir)+1); 151 | else $relativePath=$fullPath; 152 | 153 | // Remove trailing '/' as output should be relative to $dir. 154 | if($relativePath[0]=='/') 155 | $relativePath=substr($relativePath,1); 156 | 157 | array_push($r,$relativePath); 158 | } 159 | return $r; 160 | } 161 | function scandir_recursive($dir,$includeDirs=TRUE,$_prefix=''){ 162 | $dir=rtrim($dir,'\\/'); 163 | $r=array(); 164 | foreach(scandir($dir)as$f){ 165 | if($f!=='.'&&$f!=='..') 166 | if(is_dir("$dir/$f")){ 167 | $r=array_merge($r,scandir_recursive("$dir/$f",$includeDirs,"$_prefix$f/")); 168 | if($includeDirs)$r[]=$_prefix.$f; 169 | }else $r[]=$_prefix.$f; 170 | } 171 | return $r; 172 | } 173 | function human_readable_filesize($v) 174 | { 175 | if($v>=1024*1024*1024) 176 | return (floor($v/(1024*1024*1024)*10)/10)." GB"; 177 | elseif($v>=1024*1024) 178 | return (floor($v/(1024*1024)*10)/10)." MB"; 179 | elseif($v>=1024) 180 | return (floor($v/1024*10)/10)." KB"; 181 | else 182 | return $v." bytes"; 183 | } 184 | function human_readable_timespan($v) 185 | { 186 | if($v>=60*60*24) 187 | return (floor($v/(60*60*24)*10)/10)." days"; 188 | elseif($v>=60*60) 189 | return (floor($v/(60*60)*10)/10)." hours"; 190 | elseif($v>=60) 191 | return floor($v/60)." min"; 192 | else 193 | return $v." sec"; 194 | } 195 | function get_dir_count($dir,$du=false){ 196 | if(is_readable($dir)){ 197 | if($du==TRUE){ // Recursively calculate the size of the directory's descendant items. 198 | $output=shell_exec('/usr/bin/find '.escapeshellarg($dir).' |wc -l'); 199 | return intval($output)-1; 200 | } 201 | else{ 202 | // Cound the items within the directory. 203 | return count(scandir($dir))-2; // -2 to exclude the "." and ".." 204 | } 205 | } 206 | else 207 | return null; 208 | } 209 | function urlencodelite($s){ 210 | $s=rawurlencode($s); 211 | $s=str_replace('%2F','/',$s); 212 | $s=str_replace(' ','%20',$s); 213 | return $s; 214 | } 215 | $PATH=isset($_REQUEST["p"])?$_REQUEST["p"]:''; 216 | if($PATH==='' && $_SERVER['REQUEST_METHOD']==='GET'){if(!$DEFAULT_DIR)$DEFAULT_DIR=realpath('.');header('Location: ?p='.urlencodelite($DEFAULT_DIR));exit;} 217 | 218 | header('Cache-Control: no-store'); // To disable caching on browser back button. 219 | 220 | if($ALLOW_SHELL && isset($_POST["ajaxShell"])){ 221 | $COMMAND=$_POST["ajaxShell"]; 222 | $TEMP_DIR_WITH_TRAILING_SLASH='/tmp/'; 223 | if(isset($_SERVER['TEMP'])){ 224 | $TEMP_DIR_WITH_TRAILING_SLASH=$_SERVER['TEMP']; 225 | if($WINDOWS)$TEMP_DIR_WITH_TRAILING_SLASH.='\\'; 226 | else $TEMP_DIR_WITH_TRAILING_SLASH.='/'; 227 | } 228 | $STDOUT_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.STDOUT'; 229 | $RESULT_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.RESULT'; 230 | $LASTCMD_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.LASTCMD'; 231 | $LOCK_FILE=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.LOCK'; 232 | $tempExecuteFile=$TEMP_DIR_WITH_TRAILING_SLASH.'editor.php.'.($WINDOWS?'cmd':'sh'); 233 | $WD=$PATH; 234 | if($WINDOWS)$WD=str_replace('/','\\',$WD); 235 | $lastCmd=null; 236 | if(file_exists($LASTCMD_FILE))$lastCmd=file_get_contents($LASTCMD_FILE); 237 | header('Content-Type: application/json'); 238 | 239 | if(file_exists($LOCK_FILE)){ 240 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 241 | echo json_encode(array("continue"=>TRUE,"output"=>$output,"lastCmd"=>$lastCmd,"debugLockFile"=>$LOCK_FILE)); 242 | } 243 | elseif(file_exists($STDOUT_FILE)){ 244 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 245 | $result=file_get_contents($RESULT_FILE); 246 | unlink($STDOUT_FILE); 247 | unlink($RESULT_FILE); 248 | echo json_encode(array("continue"=>FALSE,"output"=>$output,"lastCmd"=>$lastCmd,"result"=>$result)); 249 | } 250 | elseif($COMMAND){ 251 | $output=null; 252 | file_put_contents($LOCK_FILE,''); 253 | file_put_contents($LASTCMD_FILE,$COMMAND); 254 | if($WINDOWS){ // No async for windows yet 255 | file_put_contents($tempExecuteFile,"cd \"".$WD."\" || exit 1\n".$SHELL_PRE."\n".$COMMAND." >>".$STDOUT_FILE."\ndel ".$LASTCMD_FILE."\ndel ".$LOCK_FILE."\ndel ".$tempExecuteFile); 256 | //shell_exec('START /B CMD /C CALL \"'+$tempExecuteFile.'\"'); 257 | shell_exec($tempExecuteFile); 258 | //usleep(100000); // So we can see some output on the first round. 259 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 260 | } 261 | else{ 262 | file_put_contents($tempExecuteFile,"#!/bin/bash\ncd ".escapeshellarg($WD)." || exit 1\n".$SHELL_PRE."\n{ ".$COMMAND."; } 2>&1\nRESULT=$?\nrm ".$LASTCMD_FILE."\nrm ".$LOCK_FILE."\nrm ".$tempExecuteFile."\n[[ \$RESULT == 0 ]] && printf success >$RESULT_FILE || printf failure >$RESULT_FILE"); 263 | shell_exec('/bin/bash '.$tempExecuteFile.' >>'.$STDOUT_FILE.' &'); 264 | //usleep(100000); // So we can see some output on the first round. 265 | $output=htmlentities(file_get_contents($STDOUT_FILE),ENT_SUBSTITUTE); 266 | } 267 | echo json_encode(array("first"=>TRUE,"continue"=>TRUE,"output"=>$output 268 | ,"debug_tempExecuteFile"=>$tempExecuteFile,"debug_command"=>$COMMAND,"debug_stdout"=>$STDOUT_FILE 269 | )); 270 | } 271 | else{ 272 | // Nothing to do. 273 | echo json_encode(array("idle"=>TRUE)); 274 | } 275 | exit(); 276 | } 277 | 278 | if(isset($_POST["rm"])){ 279 | header('Content-Type: application/json'); 280 | if(is_dir($PATH)&&!is_link($PATH)) 281 | echo '{"success":'.(rmdir($PATH)===true?'true':'false').'}'; 282 | else 283 | echo '{"success":'.(unlink($PATH)===true?'true':'false').'}'; 284 | exit; 285 | } 286 | elseif(isset($_POST["cp"])){ 287 | header('Content-Type: application/json'); 288 | if(file_exists($_POST["dest"])) 289 | echo '{"success":false,"message":"File already exists in destination."}'; 290 | else 291 | echo '{"success":'.(copy($_POST["source"],$_POST["dest"])===true?'true':'false').'}'; 292 | exit; 293 | } 294 | elseif(isset($_POST["mv"])){ 295 | header('Content-Type: application/json'); 296 | if(file_exists($_POST["dest"])) 297 | echo '{"success":false,"message":"File already exists in destination."}'; 298 | else 299 | echo '{"success":'.(rename($_POST["source"],$_POST["dest"])===true?'true':'false').'}'; 300 | exit; 301 | } 302 | elseif(isset($_POST["content"])){ 303 | header('Content-Type: application/json'); 304 | $content=$_POST["content"]; 305 | file_put_contents($PATH,$content); 306 | echo '{"msg":"Your changes have been saved."}'; 307 | exit; 308 | } 309 | elseif(isset($_POST["new"])){ 310 | header('Content-Type: application/json'); 311 | $type=$_POST["type"]; 312 | if(file_exists($PATH)) 313 | //echo '{"success":false,"message":""}'; 314 | echo json_encode(array("success"=>false,"message"=>"File already exists.")); 315 | else{ 316 | if($type==='dir'){ 317 | $r=mkdir($PATH); 318 | } 319 | elseif($type==='file'){ 320 | $r=file_put_contents($PATH,''); 321 | } 322 | //echo '{"success":'.($r!==false?'true':'false').'}'; 323 | echo json_encode(array("success"=>$r!==false,"message"=>"Error writing ".$PATH)); 324 | } 325 | exit; 326 | } 327 | elseif(isset($_GET["d"])){ 328 | // http://php.net/manual/en/function.readfile.php 329 | header('Content-Description: File Transfer'); 330 | $fi=finfo_open(FILEINFO_MIME); 331 | header('Content-Type: '.finfo_file($fi,$PATH)); 332 | finfo_close($fi); 333 | header('Content-Disposition: inline; filename='.basename($PATH)); 334 | header('Content-Transfer-Encoding: binary'); 335 | header('Cache-Control: private, max-age=0, must-revalidate'); 336 | header('Content-Length: '.filesize($PATH)); 337 | ob_clean(); 338 | flush(); 339 | readfile($PATH); 340 | exit; 341 | } 342 | elseif(isset($_FILES['file'])){ 343 | $dest=$PATH.'/'.$_FILES['file']['name']; 344 | $tempFile=$_FILES['file']['tmp_name']; 345 | header('Content-Type: application/json'); 346 | echo json_encode(array("success"=>move_uploaded_file($_FILES['file']['tmp_name'],$dest)===true)); 347 | exit; 348 | } 349 | 350 | $Recursive=FALSE; 351 | if(isset($_REQUEST["r"]))$Recursive=TRUE; 352 | $Grep=""; 353 | if(isset($_REQUEST["grep"]))$Grep=$_REQUEST["grep"]; 354 | $Find=""; 355 | if(isset($_REQUEST["find"]))$Find=$_REQUEST["find"]; 356 | $Locate=""; 357 | if(isset($_REQUEST["locate"]))$Locate=$_REQUEST["locate"]; 358 | $Title=substr($PATH,strrpos(str_replace('\\','/',$PATH),'/')+1); 359 | if($Title=='')$Title='Code Editor'; 360 | ?> 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | <?php echo $Title;?> 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 |
377 | 378 | 386 |
387 |
388 | Search:
389 |
390 | 391 | 392 | 393 |
394 |
> 395 | 396 | 397 |
398 | 399 |
&1',$errorCode); ?>
400 | 401 | 402 | 403 |
Common shell commands:
404 | du -hd1|sort -h
405 | fdupes -rq .
406 | ps -eo pcpu,time,pid,args|tail -n +2|sort -nrk1
407 | iotop -obn1
408 | 409 | > 410 |
411 | 412 |
413 |
414 | "; 429 | $c=file_get_contents($PATH); 430 | if(substr($c,0,3)==pack("CCC",0xef,0xbb,0xbf)) // Remove BOM 431 | $c=substr($c,3); 432 | $html.="
" . htmlentities($c,ENT_SUBSTITUTE) . "
"; 433 | return $html; 434 | } 435 | function getFiles(){ 436 | global $PATH,$Recursive,$Locate,$DIRS_AT_TOP; 437 | if($Locate) 438 | $r=getFilesUsingLocate($PATH,$Locate); 439 | elseif($Recursive) 440 | $r=scandir_recursive($PATH); 441 | else 442 | $r=scandir($PATH); 443 | sort($r,SORT_STRING|SORT_FLAG_CASE); 444 | 445 | if($DIRS_AT_TOP && !$Locate && !$Recursive){ 446 | $files=array(); 447 | $dirs=array(); 448 | foreach($r as $f) 449 | { 450 | if(is_dir($PATH.'/'.$f)) 451 | array_push($dirs,$f); 452 | else 453 | array_push($files,$f); 454 | } 455 | $r=array_merge($dirs,$files); 456 | } 457 | return $r; 458 | } 459 | function renderFileList() 460 | { 461 | global $PATH,$Grep,$Find,$Recursive; 462 | $editorPPath=realpath($_SERVER["DOCUMENT_ROOT"]); 463 | $files=getFiles(); 464 | $html=""; 465 | $html.="
"; 466 | if ($PATH != ""){ 467 | $PathEscaped=str_replace("'","\\'",$PATH); 468 | $delOnclick="onclick=\""."if(editor.del('$PathEscaped',null)===true){window.location=$('#list>.dir:first a:first').attr('href');}return(false)\""; 469 | if(get_dir_count($PATH)>0&&!is_link($PATH)) # Hide del button if dir not empty and not a symlink. 470 | $delOnclick=''; 471 | $up=substr($PATH,0, strrpos($PATH,'/')); 472 | if($up==='') 473 | $up='/'; 474 | elseif($Recursive) 475 | $up=$PATH; # Back out of search/recursive mode instead of up a level. 476 | $html.=""; 477 | } 478 | foreach($files as $filePath) 479 | { 480 | $rFile=$filePath; 481 | $aFile=($PATH=='/'?'':$PATH).'/'.$rFile; 482 | $pFile=realpath($aFile); 483 | if($rFile=='.'||$rFile=='..') 484 | continue; 485 | $isDir=is_dir($aFile); 486 | 487 | 488 | if($Find){ 489 | if(preg_match('/'.$Find.'/i',$rFile)!==1) 490 | continue; 491 | } 492 | if($Grep!=''){ 493 | if($isDir)continue; 494 | $fileContents=file_get_contents($aFile,false,null,0,5242880); // Limit to 5MB 495 | if(strpos(strtolower($fileContents),strtolower($Grep))===FALSE) 496 | continue; 497 | } 498 | 499 | if($isDir){ 500 | $size=get_dir_count($aFile); 501 | $friendlySize=$size; 502 | } 503 | else{ 504 | $size=filesize($aFile); 505 | $friendlySize=human_readable_filesize(filesize($aFile)); 506 | } 507 | $age=human_readable_timespan(time()-filemtime($aFile)); 508 | $direct=''; 509 | if(strpos($pFile,$editorPPath)===0){ 510 | $direct=urlencodelite(str_replace('\\','/',substr($pFile,strlen($editorPPath)))); 511 | if($direct=='')$direct='/'; 512 | } 513 | $aFileEscaped=str_replace("'","\\'",$aFile); 514 | if($isDir) 515 | $dlAnchor=''; 516 | else 517 | $dlAnchor=''; 518 | $delOnclick="onclick=\"editor.del('$aFileEscaped',this);return(false)\""; 519 | if($isDir&&$size!==0&&!is_link($aFile))$delOnclick=''; # Hide del button if dir not empty and not a symlink. 520 | 521 | $segAs=''; 522 | 523 | // Anchor each path segment. 524 | $segs=explode('/',$rFile); 525 | $segsCount=count($segs); 526 | $segAppend=''; 527 | for($i=0;$i<$segsCount;$i++){ 528 | $segAppend.='/'.$segs[$i]; 529 | if($i===$segsCount-1 && ($size>=1048576 || preg_match('/\.(mp3|aac|ogg|wav|mid|jpg|bmp|gif|png|webp|webm|mp4|mkv|m4v|avi|pdf|zip|rar|tar|gz|7z)$/i',$rFile)===1)) 530 | // Don't allow editing if file is over 1MB or is a media type. 531 | $href=$direct; 532 | else 533 | $href='?p='.urlencodelite(($PATH==='/'?'':$PATH).$segAppend); 534 | $segIsDir=$i!==$segsCount-1; 535 | $segAs.=' / '.$segs[$i].''; 536 | } 537 | $segAs=substr($segAs,28); // Trim leading " / " 538 | 539 | $pasteAnchor=''; 540 | if($isDir) 541 | $pasteAnchor=""; 542 | 543 | $html.='
'.$segAs."
$friendlySize$age$dlAnchor$pasteAnchor
"; 544 | } 545 | $html.="
"; 546 | return $html; 547 | } 548 | if(isset($BRANDING_FOOTER))echo $BRANDING_FOOTER; 549 | ?> 550 | 551 | $(function(){$(".shellButton").triggerHandler("click");});'; // If was postback then shell form is already open so need to trigger click to init form events ?> 552 | 553 | 554 | --------------------------------------------------------------------------------