URL: https://github.com/adamjimenez/shiftedit-ajax Edit the username and password below */ //set error level error_reporting(E_ALL ^ E_NOTICE); ini_set('display_errors', '0'); ini_set('display_startup_errors', '0'); set_error_handler('error_handler'); register_shutdown_function('shutdown'); //config $host = '{$host}'; $username = '{$username}'; // username or ftp username $password = '{$password}'; // password or ftp password $dir = '{$dir}'; // path to files e.g. dirname(__FILE__).'/'; $server_type = '{$server_type}'; // local, ftp or sftp. local requires webserver to have write permissions to files. $pasv = '{$pasv}'; // true for pasv mode / false for active mode $port = '{$port}'; // usually 21 for ftp and 22 for sftp $definitions = '{$definitions}'; // autocomplete definitions e.g. http://example.org/defs.json $phpseclib_path = ''; // path to phpseclib for sftp, get from: https://github.com/phpseclib/phpseclib $origin = $_SERVER['HTTP_ORIGIN'] ?: '{$origin}'; // CORS origin: https://shiftedit.net // restrict access by ip $ip_restrictions = false; // allowed ips. get your ip from https://www.google.co.uk/search?q=ip+address $ips = []; // api version $version = '1.3'; function shutdown() { if ($error = error_get_last()) { error_handler($error['type'], $error['message'], $error['file'], $error['line']); } } function error_handler($errno, $errstr, $errfile, $errline, $errcontext = "") { switch ($errno) { case E_USER_ERROR: case E_ERROR: case E_PARSE: case E_CORE_ERROR: case E_COMPILE_ERROR: $response = []; $response['success'] = false; $response['error'] = $errstr.' on line '.$errline; echo json_encode($response); exit; break; } } //include path if ($phpseclib_path) { set_include_path(get_include_path() . PATH_SEPARATOR . $phpseclib_path); } //prevent magic quotes if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { function stripslashes_gpc(&$value) { $value = stripslashes($value); } array_walk_recursive($_GET, 'stripslashes_gpc'); array_walk_recursive($_POST, 'stripslashes_gpc'); } // CORS Allow from shiftedit if (isset($_SERVER['HTTP_ORIGIN'])) { header('Access-Control-Allow-Origin: ' . $origin); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 86400'); } // Access-Control headers are received during OPTIONS requests if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) { header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); } if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"); } exit; } $cookie_secure = ($_SERVER['HTTPS'] === 'on'); $cookie_params = session_get_cookie_params(); if(PHP_VERSION_ID < 70300) { session_set_cookie_params($cookie_params['lifetime'], '/; SameSite=None', $cookie_params['domain'], $cookie_secure, $cookie_params['httponly']); } else { session_set_cookie_params([ 'lifetime' => $cookie_params['lifetime'], 'path' => '/', 'domain' => $cookie_params['domain'], 'secure' => $cookie_secure, 'httponly' => $cookie_params['httponly'], 'samesite' => 'None' ]); } session_start(); try { //ip restrictions if ($ip_restrictions and !in_array($_SERVER['REMOTE_ADDR'], $ips)) { throw new Exception('access denied, ip restrictions in effect'); } //authentication if ($username and !$_SESSION['shiftedit_logged_in']) { if ($username !== $_POST['user'] || sha1($password) !== $_POST['pass']) { //delay to protect against brute force attack sleep(1); throw new Exception('Login incorrect'); } $_SESSION['shiftedit_logged_in'] = true; } header('Content-Type: application/json, charset=utf-8'); abstract class server { function __construct() { //max upload size $this->max_size = 20000000; $this->max_files = $_SESSION['prefs']['maxFiles'] ?: 1000; $this->ftp_log = []; $this->startedAt = 0; $this->output_log = true; $this->text_files = [ 'css', 'htaccess', 'html', 'jade', 'java', 'js', 'json', 'less', 'md', 'php', 'pl', 'py', 'rb', 'scala', 'scss', 'sh', 'svg', 'txt', 'xml', 'yaml', ]; } function send_msg($id, $msg) { echo "id: $id" . PHP_EOL; echo "data: {\n"; echo "data: \"msg\": \"$msg\", \n"; echo "data: \"id\": $id\n"; echo "data: }\n"; echo PHP_EOL; ob_flush(); flush(); } function log($msg) { $this->ftp_log[] = $msg; if ($this->startedAt) { $this->send_msg($this->startedAt, $msg); } } function chmod_num($permissions) { $mode = 0; if ($permissions[1] == 'r') $mode += 0400; if ($permissions[2] == 'w') $mode += 0200; if ($permissions[3] == 'x') $mode += 0100; else if ($permissions[3] == 's') $mode += 04100; else if ($permissions[3] == 'S') $mode += 04000; if ($permissions[4] == 'r') $mode += 040; if ($permissions[5] == 'w') $mode += 020; if ($permissions[6] == 'x') $mode += 010; else if ($permissions[6] == 's') $mode += 02010; else if ($permissions[6] == 'S') $mode += 02000; if ($permissions[7] == 'r') $mode += 04; if ($permissions[8] == 'w') $mode += 02; if ($permissions[9] == 'x') $mode += 01; else if ($permissions[9] == 't') $mode += 01001; else if ($permissions[9] == 'T') $mode += 01000; return sprintf('%o', $mode); } function mkdir_recursive($path) { $parts = explode('/', $path); $path = ''; foreach ($parts as $part) { $path .= $part; if (!$this->file_exists($path)) { $result = $this->mkdir($path); if ($result === false) { return false; } } $path .= '/'; } return true; } function close() {} } if ($server_type === 'local') { class local extends server { function __construct() { global $dir; // prefix dir with slash if (strlen($dir) and substr($dir, 0, 1) != '/') { $dir = '/'.$dir; } $this->dir = $dir; } function chdir($path) { if ($path === $this->pwd) { return true; } else { if (chdir($path)) { $this->pwd = $path; return true; } else { return false; } } } function get($remote_file) { $path = $this->dir.$remote_file; return file_get_contents($path); } function put($file, $content, $resume_pos = 0) { if (!$file) { return false; } $path = $this->dir.$file; $fp = fopen($path, 'w'); fseek($fp, $resume_pos); $result = fwrite($fp, $content); fclose($fp); return $result; } function last_modified($file) { $file = $this->dir.$file; return filemtime($file); } function is_dir($dir) { $dir = $this->dir.$dir; return is_dir($dir); } function file_exists($file) { $file = $this->dir.$file; return file_exists($file); } function chmod($mode, $file) { $file = $this->dir.$file; return chmod($mode, $file); } function rename($old_name, $new_name) { $old_name = $this->dir.$old_name; $new_name = $this->dir.$new_name; return rename($old_name, $new_name); } function mkdir($dir) { $dir = $this->dir.$dir; return mkdir($dir); } function delete($file) { if (!$file) { $this->log[] = 'no file'; return false; } $path = $this->dir.$file; if ($this->is_dir($file)) { $list = $this->parse_raw_list($file); if (!is_array($list)) { return false; } foreach ($list as $item) { if ($item['name'] != '..' && $item['name'] != '.') { $this->delete($file.'/'.$item['name']); } } chdir('../'); $this->log('rmdir '.$file); if (!rmdir($path)) { return false; } else { return true; } } else { if ($this->file_exists($file)) { $this->log('delete '.$file); return unlink($path); } } } function parse_raw_list($path) { $path = $this->dir.$path; if ($path and !$this->chdir($path)) { return false; } $d = dir($path); if ($d === false) { return false; } $items = []; while (false !== ($entry = $d->read())) { $items[] = array( 'name' => $entry, 'permsn' => substr(decoct(fileperms($entry)), 2), 'size' => (int)filesize($entry), 'modified' => filemtime($entry), 'type' => is_dir($entry) ? 'folder' : 'file', ); } $d->close(); return $items; } function search_nodes($s, $path) { $list = $this->parse_raw_list($path); if (!is_array($list)) { return []; } $items = []; foreach ($list as $v) { if ($v['type'] != 'file') { if ($v['name'] == '.' or $v['name'] == '..' or $v['name'] == '.svn') { continue; } $arr = $this->search_nodes($s, $path.$v['name'].'/'); $items = array_merge($items, $arr); } else { if (strstr($v['name'], $s)) { $items[] = $path.$v['name']; $this->send_msg($this->startedAt, $path.$v['name']); } } } return $items; } function search($s, $path) { $this->startedAt = time(); return $this->search_nodes($s, $path); } } } elseif ($server_type === 'ftp') { class ftp extends server { function connect($host, $user, $pass, $port = 21, $dir, $options = []) { $pasv = $options['pasv'] ? $options['pasv'] : true; $logon_type = $options['logon_type']; $encryption = $options['encryption']; if (!$host) { $this->ftp_log[] = 'No domain'; return false; } if (!$options['timeout']) { $options['timeout'] = 10; } if (!function_exists('ftp_connect')) { $this->ftp_log[] = 'PHP FTP module is not installed'; return false; } if (function_exists('ftp_ssl_connect')) { $this->conn_id = ftp_ssl_connect($host, $port, $options['timeout']); } else { $this->conn_id = ftp_connect($host, $port, $options['timeout']); } if (!$this->conn_id) { $this->ftp_log[] = 'connection to host failed'; return false; } if ($encryption) { $result = ftp_login($this->conn_id, $user, $pass); } else { $this->command("USER ".$user); $result = $this->command("PASS ".$pass); } if (substr($result, 0, 3) == '530') { $this->require_password = true; } if ($pasv) { ftp_pasv($this->conn_id, true); } // prefix dir with slash if (strlen($dir) and substr($dir, 0, 1) != '/') { $dir = '/'.$dir; } $this->dir = $dir; if (substr($result, 0, 3) !== '230' and $result !== true) { return false; } elseif ($dir and !$this->chdir($dir)) { $this->ftp_log[] = 'Dir does not exist: '.$dir; return false; } else { return true; } } function command($command) { $result = ftp_raw($this->conn_id, $command); if (substr($command, 0, 5) == 'PASS ') { $command = 'PASS ******'; } $this->ftp_log[] = $command; $this->ftp_log = array_merge($this->ftp_log, $result); return trim(end($result)); } function chdir($path) { if ($path === $this->pwd) { return true; } else { $this->ftp_log[] = 'chdir '.$path; if (@ftp_chdir($this->conn_id, $path)) { $this->pwd = $path; return true; } else { return false; } } } function get($remote_file, $get_file = false) { $remote_file = $this->dir.$remote_file; //check file size $size = ftp_size($this->conn_id, $remote_file); if ($size > $this->max_size) { $this->ftp_log[] = 'File too large: '.file_size($size); return false; } $tmpdir = sys_get_temp_dir() or die('failed to get tmp dir'); $tmpfname = tempnam($tmpdir, "shiftedit_ftp_") or die('failed to create tmp file'); $handle = fopen($tmpfname, "w+") or die('failed to open tmp file'); if (ftp_fget($this->conn_id, $handle, $remote_file, FTP_BINARY)) { if ($get_file) { fclose($handle); return $tmpfname; } rewind($handle); $data = stream_get_contents($handle, $this->max_size); fclose($handle); unlink($tmpfname); return $data; } else { fclose($handle); unlink($tmpfname); return false; } } function put($file, $content, $resume_pos = 0) { $mode = FTP_BINARY; if (!$file) { return false; } $path = $this->dir.$file; $tmp = tmpfile(); if (fwrite($tmp, $content) === false) { $this->ftp_log[] = 'can\'t write to filesystem'; return false; } rewind($tmp); $this->chdir(dirname($path)); if ($resume_pos) { ftp_raw($this->conn_id, "REST ".$resume_pos); } $result = ftp_fput($this->conn_id, basename_safe($path), $tmp, $mode); //try deleting first if ($result === false) { $items = $this->parse_raw_list(dirname($file)); if (!is_array($items)) { return false; } $perms = 0; foreach ($items as $v) { if ($v['name'] == basename($file)) { $perms = $v['permsn']; } } //delete before save otherwise save does not work on some servers $this->delete($file); if ($perms) { $this->chmod($perms, $file); } if ($resume_pos) { ftp_raw($this->conn_id, "REST ".$resume_pos); } $result = ftp_fput($this->conn_id, basename_safe($path), $tmp, $mode); } fclose($tmp); return $result; } function last_modified($file) { $file = $this->dir.$file; return ftp_mdtm($this->conn_id, $file); } function size($file) { $file = $this->dir.$file; return ftp_size($this->conn_id, $file); } function is_dir($dir) { $dir = $this->dir.$dir; // Get the current working directory $origin = ftp_pwd($this->conn_id); // Attempt to change directory, suppress errors return ftp_size($this->conn_id, $dir) === -1; } function file_exists($file) { $file = $this->dir.$file; if (ftp_size($this->conn_id, $file) == '-1') { //folder? if ($this->chdir($file)) { return true; } return false; } else { return true; } } function chmod($mode, $file) { $file = $this->dir.$file; return ftp_chmod($this->conn_id, intval($mode, 8), $file); } function rename($old_name, $new_name) { $old_name = $this->dir.$old_name; $new_name = $this->dir.$new_name; return ftp_rename($this->conn_id, $old_name, $new_name); } function mkdir($dir) { $dir = $this->dir.$dir; return ftp_mkdir($this->conn_id, $dir) !== false ? true : false; } function delete($file) { if (!$file) { $this->ftp_log[] = 'no file'; return false; } $path = $this->dir.$file; if ($this->is_dir($file)) { $list = $this->parse_raw_list($file); if (!is_array($list)) { return false; } foreach ($list as $item) { if ($item['name'] != '..' && $item['name'] != '.') { $this->delete($file.'/'.$item['name']); } } if (!$this->chdir(dirname($path))) { return false; } $this->log('rmdir '.$file); if (!ftp_rmdir($this->conn_id, basename($path))) { return false; } else { return true; } } else { if ($this->file_exists($file)) { $this->log('delete '.$file); return ftp_delete($this->conn_id, $path); } } } function parse_raw_list($path) { $path = $this->dir.$path; $array = ftp_rawlist($this->conn_id, '-a '.$path); if ($array === false) { return false; } $items = []; //$systype = ftp_systype($this->conn_id); foreach ($array as $folder) { $struc = []; if (preg_match("/([0-9]{2})-([0-9]{2})-([0-9]+) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|) +(.+)/", $folder, $split)) { if (is_array($split)) { if ($split[3] < 70) { $split[3] += 2000; } else { $split[3] += 1900; } // 4digit year fix $struc['month'] = $split[1]; $struc['day'] = $split[2]; if (strlen($split[3]) == 4) { $struc['year'] = $split[3]; $struc['time'] = '00:00'; } else { $struc['year'] = date('Y'); $struc['time'] = $split[3]; if (strtotime($struc['month'].' '.$struc['day'].' '.$struc['year'].' '.$struc['time']) > time()) { $struc['year'] -= 1; } } $struc['modified'] = strtotime($struc['month'].' '.$struc['day'].' '.$struc['year'].' '.$struc['time']); $struc['name'] = $split[8]; if ($split[7] == "") { $struc['type'] = 'folder'; } else { $struc['type'] = 'file'; $struc['size'] = $split[7]; } } } else { $current = preg_split("/[\s]+/", $folder, 9); $i = 0; $struc['perms'] = $current[0]; $struc['permsn'] = $this->chmod_num($struc['perms']); $struc['number'] = $current[1]; $struc['owner'] = $current[2]; $struc['group'] = $current[4]; $struc['size'] = $current[(count($current)-5)]; $struc['month'] = $current[(count($current)-4)]; $struc['day'] = $current[(count($current)-3)]; $date = $current[(count($current)-2)]; $struc['name'] = str_replace('//', '', end($current)); if (strlen($date) == 4) { $struc['year'] = $date; $struc['time'] = '00:00'; } else { $struc['year'] = date('Y'); if (strtotime($struc['month'].' '.$struc['day']) > time()) { $struc['year']--; } $struc['time'] = $date; } $struc['modified'] = strtotime($struc['month'].' '.$struc['day'].' '.$struc['year'].' '.$struc['time']); $struc['raw'] = $folder; if (substr($folder, 0, 1) == "d") { $struc['type'] = 'folder'; } elseif (substr($folder, 0, 1) == "l") { $struc['type'] = 'link'; continue; } else { $struc['type'] = 'file'; } } if ($struc['name']) { $items[] = $struc; } } return $items; } function search_nodes($s, $path) { $list = $this->parse_raw_list($path); if (!is_array($list)) { return []; } $items = []; foreach ($list as $v) { $file_path = $path . $v['name']; if ($v['type'] != 'file') { if (in_array($v['name'], ['.', '..', '.svn', '.git'])) { continue; } $arr = $this->search_nodes($s, $file_path . '/'); $items = array_merge($items, $arr); } else { if (strstr($v['name'], $s)) { $items[] = $file_path; $this->send_msg($this->startedAt, $file_path); } // search content of text files if (in_array(file_ext($v['name']), $this->text_files)) { $content = $this->get($file_path); if (stristr($content, $s)) { $items[] = $file_path; $this->send_msg($this->startedAt, $file_path); } } } } return $items; } function search($s, $path) { $this->startedAt = time(); return $this->search_nodes($s, $path); } function close() { ftp_close($this->conn_id); } } } else if ($server_type === 'sftp') { if ($phpseclib_path) { class sftp extends server { function __construct() { $this->debug = false; parent::__construct(); require_once('Net/SFTP.php'); if ($this->debug) { define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX); } } function errorHandler($errno, $errstr, $errfile, $errline) { if ($this->debug) { print $errstr." in "; print $errfile." on line "; print $errline."\n"; } if ($errno === E_USER_NOTICE) { $this->ftp_log[] = $errstr; $this->failed = true; } } function connect($host, $user, $pass, $port = 22, $dir, $options = []) { if (!$host) { return false; } $this->ftp_log = []; $this->failed = false; $logon_type = $options['logon_type']; if (!$options['timeout']) { $options['timeout'] = 10; } set_error_handler(array($this, 'errorHandler')); $this->sftp = new Net_SFTP($host, $port, $options['timeout']); if (!$this->sftp) { $this->ftp_log[] = 'connection to host failed'; $this->ftp_log[] = $this->sftp->getSFTPLog(); return false; } if ($this->failed) { return; } if ($logon_type == 'key') { if (!$private_key) { $this->ftp_log[] = 'missing key - set a key from your account'; return false; } require_once("Crypt/RSA.php"); $pass = new Crypt_RSA(); if (!$pass->loadKey($private_key)) { $this->ftp_log[] = 'invalid key'; return false; } } if ($this->sftp->login($user, $pass) === false) { $stars = ''; for ($i = 0; $i < strlen($pass); $i++) { $stars .= '*'; } $this->ftp_log[] = $this->sftp->getLog(); $this->ftp_log[] = $this->sftp->getSFTPLog(); if ($logon_type == 'key') { $this->ftp_log[] = 'Can\'t connect with key'; } else { $this->ftp_log[] = 'login incorrect
User: '.$user.'
Pass: '.$stars.'
'.$log; $this->require_password = true; } return false; } $this->dir = $dir; if (!$this->sftp) { return false; } elseif ($dir and !$this->sftp->stat($dir)) { $this->ftp_log[] = 'Dir does not exist: '.$dir; return false; } else { return true; } } function get($remote_file) { $remote_file = $this->dir.$remote_file; //check file size $size = $this->sftp->size($remote_file); if ($size > $this->max_size) { $this->ftp_log[] = 'File too large: '.file_size($size); return false; } $data = $this->sftp->get($remote_file); if ($data === false) { return false; } return $data; } function put($remote_file, $content, $resume_pos = -1) { $remote_file = $this->dir.$remote_file; return $this->sftp->put($remote_file, $content, NET_SFTP_STRING, $resume_pos); } function last_modified($file) { $file = $this->dir.$file; $stat = $this->sftp->stat($file); return $stat['mtime']; } function size($file) { $file = $this->dir.$file; return $this->sftp->size($file); } function is_dir($file) { $file = $this->dir.$file; $stat = $this->sftp->stat($file); return ($stat['type'] == 2) ? true : false; } function file_exists($file) { $file = $this->dir.$file; $stat = $this->sftp->stat($file); return $stat ? true : false; } function chmod($mode, $file) { $file = $this->dir.$file; return $this->sftp->chmod($mode, $file); } function rename($old_name, $new_name) { $old_name = $this->dir.$old_name; $new_name = $this->dir.$new_name; return $this->sftp->rename($old_name, $new_name); } function mkdir($dir) { $dir = $this->dir.$dir; return $this->sftp->mkdir($dir); } function delete($file) { if (!$file) { return false; } $path = $this->dir.$file; return $this->sftp->delete($path, true); } function parse_raw_list($subdir) { $path = $this->dir.$subdir; $items = []; $files = $this->sftp->rawlist($path); // List all the files $i = 0; foreach ($files as $file => $stat) { if ($file != '.' and $file != '..') { $items[$i]['name'] = $file; $items[$i]['permsn'] = $stat['permissions']; if ($stat['type'] == 1) { $items[$i]['type'] = 'file'; $items[$i]['size'] = (int)$stat['size']; } elseif ($stat['type'] == 2) { $items[$i]['type'] = 'folder'; } else { //ignore symlinks continue; } $items[$i]['modified'] = $stat['mtime']; } $i++; } return $items; } function search_nodes($s, $path) { $packet_handler = function($string) { $items = explode("\n", trim($string)); $items = array_unique($items); sort($items); foreach ($items as $k => $v) { if (strstr($v, ':')) { continue; } $v = substr($v, strlen($dir)); $this->send_msg($this->startedAt, $v); } }; $dir = $this->dir.$path; $this->exec("pkill grep"); $this->exec("pkill find"); $results = $this->exec('grep -Ilr '.escapeshellarg($s).' '.escapeshellarg($dir), $packet_handler); $results .= $this->exec('find '.escapeshellarg($dir).' -name "'.escapeshellarg($s).'*"', $packet_handler); } function search($s, $path) { $this->startedAt = time(); return $this->search_nodes($s, $path); } function close() { if ($this->sftp) { $this->sftp->__destruct(); } } } } else if (function_exists('ssh2_connect')) { class sftp extends server { function connect($host, $user, $pass, $port = 22, $dir, $options = []) { if (!function_exists('ssh2_connect')) { $this->ftp_log[] = 'PHP SSH2 module not loaded'; return false; } $logon_type = $options['logon_type']; if (!$options['timeout']) { $options['timeout'] = 10; } if (!$host) { $this->ftp_log[] = 'No domain'; return false; } if (!$options['timeout']) { $options['timeout'] = 10; } $this->conn_id = ssh2_connect($host, $port); if (!$this->conn_id) { $this->ftp_log[] = 'connection to host failed'; return false; } $result = ssh2_auth_password($this->conn_id, $user, $pass); if (!$result) { $this->ftp_log[] = 'login incorrect'; $this->require_password = true; } //print var_dump((stream_get_contents(ssh2_exec($this->conn_id, 'pwd')))); exit; $this->sftp = ssh2_sftp($this->conn_id); if ($this->sftp === null) { die('can\'t establish sftp'); } $this->dir = $dir; if (substr($result, 0, 3) !== '230' and $result !== true) { return false; } elseif ($dir and !$this->chdir($dir)) { $this->ftp_log[] = 'Dir does not exist: '.$dir; return false; } else { return true; } } function chdir($path) { if ($path === $this->pwd) { return true; } else { $this->ftp_log[] = 'chdir '.$path; if ($this->exec('cd '.$path.'; pwd').'/' === $path) { $this->pwd = $this->exec('pwd'); return true; } else { return false; } } } function get($remote_file, $mode = FTP_BINARY, $resume_pos = null) { $remote_file = $this->dir.$remote_file; //check file size $stat = ssh2_sftp_stat($this->sftp, $remote_file); $size = $stat['size']; if ($size > $this->max_size) { $this->ftp_log[] = 'File too large: '.file_size($size); return false; } $handle = fopen("ssh2.sftp://".$this->sftp."/".$remote_file, 'r'); if ($handle) { $data = stream_get_contents($handle, $this->max_size); fclose($handle); return $data; } else { fclose($handle); return false; } } function put($file, $content, $resume_pos = -1) { $remote_file = $this->dir.$file; $handle = fopen("ssh2.sftp://".$this->sftp."/".$remote_file, 'w'); if ($resume_pos != -1) { fseek($handle, $resume_pos); } return fwrite($handle, $content); } function last_modified($file) { $remote_file = $this->dir.$file; $stat = ssh2_sftp_stat($this->sftp, $remote_file); return $stat['mtime']; } function size($file) { $remote_file = $this->dir.$file; $stat = ssh2_sftp_stat($this->sftp, $remote_file); return $stat['size']; } function is_dir($dir) { $dir = $this->dir.$dir; // Get the current working directory $origin = $this->exec('pwd'); // Attempt to change directory, suppress errors if (@$this->chdir($dir)) { // If the directory exists, set back to origin $this->chdir($origin); return true; } // Directory does not exist return false; } function file_exists($file) { $file = $this->dir.$file; $stat = ssh2_sftp_stat($this->sftp, $file); if ($stat['size'] == '-1') { //folder? if ($this->chdir($file)) { return true; } return false; } else { return true; } } function chmod($mode, $file) { $file = $this->dir.$file; return ssh2_sftp_chmod($this->sftp, $file, $mode); } function rename($old_name, $new_name) { $old_name = $this->dir.$old_name; $new_name = $this->dir.$new_name; return ssh2_sftp_rename($this->sftp, $old_name, $new_name); } function mkdir($dir) { $dir = $this->dir.$dir; return ssh2_sftp_mkdir($this->sftp, $dir) !== false ? true : false; } function delete($file) { if (!$file) { $this->ftp_log[] = 'no file'; return false; } $path = $this->dir.$file; if ($this->is_dir($file)) { $list = $this->parse_raw_list($file); if (!is_array($list)) { return false; } foreach ($list as $item) { if ($item['name'] != '..' && $item['name'] != '.') { $this->delete($file.'/'.$item['name']); } } if (!$this->chdir(dirname($path))) { return false; } $this->ftp_log[] = 'rmdir '.$path; if (!ssh2_sftp_rmdir($this->sftp, basename($path))) { return false; } else { return true; } } else { if ($this->file_exists($file)) { $this->ftp_log[] = 'delete '.$path; return ssh2_sftp_unlink($this->sftp, $path); } } } function parse_raw_list($subdir = '') { $path = $this->dir.$subdir; $items = []; $list = $this->exec('ls -al '.escapeshellarg($path)); $files = explode("\n", $list); // List all the files $i = 0; foreach ($files as $folder) { if (preg_match("/total [\d]+/", $folder)) { continue; } $struc = []; $current = preg_split("/[\s]+/", $folder, 9); $i = 0; $struc['perms'] = $current[0]; $struc['permsn'] = $this->chmod_num($struc['perms']); $struc['number'] = $current[1]; $struc['owner'] = $current[2]; $struc['group'] = $current[4]; $struc['size'] = $current[(count($current)-5)]; $struc['month'] = $current[(count($current)-4)]; $struc['day'] = $current[(count($current)-3)]; $date = $current[(count($current)-2)]; $struc['name'] = str_replace('//', '', end($current)); if (strlen($date) == 4) { $struc['year'] = $date; $struc['time'] = '00:00'; } else { $struc['year'] = date('Y'); if (strtotime($struc['month'].' '.$struc['day']) > time()) { $struc['year']--; } $struc['time'] = $date; } $struc['modified'] = strtotime($struc['month'].' '.$struc['day'].' '.$struc['year'].' '.$struc['time']); $struc['raw'] = $folder; if (substr($folder, 0, 1) == "d") { $struc['type'] = 'folder'; } elseif (substr($folder, 0, 1) == "l") { $struc['type'] = 'link'; $pos = strpos($struc['name'], ' -> '); if ($pos) { $struc['name'] = substr($struc['name'], 0, $pos); } } else { $struc['type'] = 'file'; } if ($struc['name']) { $items[] = $struc; } $i++; } return $items; } function search_nodes($s, $path) { $packet_handler = function($string) { global $dir; $items = explode("\n", trim($string)); $items = array_unique($items); sort($items); foreach ($items as $k => $v) { if (strstr($v, ':')) { continue; } $v = substr($v, strlen($dir)); if ($v) { $this->send_msg($this->startedAt, $v); } } }; $dir = $this->dir.$path; $this->exec("pkill grep"); $this->exec("pkill find"); $results = $this->exec('grep -Ilr '.escapeshellarg($s).' '.escapeshellarg($dir)); $results .= $this->exec('find '.escapeshellarg($dir).' -name "'.escapeshellarg($s).'*"'); $packet_handler($results); } function search($s, $path) { $this->startedAt = time(); return $this->search_nodes($s, $path); } function close() {} function exec($command) { $stream = ssh2_exec($this->conn_id, $command); if (!$stream) { return false; } stream_set_blocking($stream, true); $result = stream_get_contents($stream); if (substr($command, 0, 5) == 'PASS ') { $command = 'PASS ******'; } $this->ftp_log[] = $command; if ($result) { $this->ftp_log[] = $result; } return trim($result); } } } else if (function_exists('curl_version')) { class sftp extends server { function cd($path) { curl_setopt($this->curl, CURLOPT_URL, "sftp://".$this->host.':'.$this->port.'/'.$path); } function connect($host, $user, $pass, $port = 22, $dir, $options = []) { $this->debug = false; if (!function_exists('curl_version')) { $this->log('PHP curl not loaded'); return false; } if (!$host) { return false; } if (!$port) { $port = 22; } $this->failed = false; if (!$options['timeout']) { $options['timeout'] = 10; } $this->timeout = $options['timeout']; $this->host = $host; $this->port = $port; $this->dir = $dir; $this->curl = curl_init(); $this->cd($this->dir); curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1); if ($options['logon_type'] == 'key') { global $auth; if (!$auth->user['private_key']) { $this->log('missing key - set a key from your account'); return false; } $this->keyfile = tempnam(); file_put_contents($this->keyfile, $auth->user['private_key']); curl_setopt($this->curl, CURLOPT_SSH_PRIVATE_KEYFILE, $this->keyfile); curl_setopt($this->curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY); } else { curl_setopt($this->curl, CURLOPT_USERPWD, $user.":".$pass); } return true; } function meta($remote_file) { // caching if ($this->meta[$remote_file]) { return $this->meta[$remote_file]; } $dir = dirname($remote_file); if ($dir === '.') { $dir = ''; } $files = $this->parse_raw_list($dir); if (!is_array($files)) { return false; } foreach ($files as $v) { $this->meta[$dir.'/'.$v['name']] = $v; } return $this->meta[$remote_file] ?: false; } function get($remote_file) { $path = $this->dir.$remote_file; //check file size $size = $this->size($remote_file); if ($size > $this->max_size) { $this->log('File too large: '.file_size($size)); return false; } $this->cd($path); $data = curl_exec($this->curl); if ($data === false) { return false; } return $data; } function put($remote_file, $content, $resume_pos = -1) { $path = $this->dir.$remote_file; $this->cd($path); $tmp = tmpfile(); if (fwrite($tmp, $content) === false) { $this->log('can\'t write to filesystem'); return false; } rewind($tmp); curl_setopt($this->curl, CURLOPT_UPLOAD, 1); curl_setopt($this->curl, CURLOPT_INFILE, $tmp); curl_setopt($this->curl, CURLOPT_INFILESIZE, strlen($content)); curl_exec($this->curl); $error_no = curl_errno($this->curl); fclose($tmp); $this->curl = curl_init(); curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1); if ($error_no === 0) { return true; } else { $this->log(curl_error($this->curl)); return false; } } function last_modified($file) { $meta = $this->meta($file); return $meta['modified']; } function size($file) { $meta = $this->meta($file); return $meta['size']; } function is_dir($file) { $meta = $this->meta($file); return $meta['type'] === 'folder'; } function file_exists($file) { $meta = $this->meta($file); return $meta !== false ? true : false; } function chmod($mode, $file) { $path = $this->dir.$file; return $this->exec('chmod '.$mode.' "'.$path.'"'); } function rename($old_name, $new_name) { $old_name = $this->dir.$old_name; $new_name = $this->dir.$new_name; return $this->exec('rename "'.$old_name.'" "'.$new_name.'"'); } function mkdir($dir) { $path = $this->dir.$dir; return $this->exec('mkdir "'.$path.'"'); } function delete($file) { if (!$file) { $this->log('no file'); return false; } $path = $this->dir.$file; if ($this->is_dir($file)) { $list = $this->parse_raw_list($file); if (!is_array($list)) { return false; } foreach ($list as $item) { if ($item['name'] != '..' && $item['name'] != '.') { return $this->exec('rm "'.$file.'/'.$item['name'].'"'); } } if (!$this->exec('rmdir "'.$path.'"')) { return false; } else { return true; } } else { if ($this->file_exists($file)) { return $this->exec('rm "'.$path.'"'); } } } function parse_raw_list($dir = '') { $items = []; $this->cd($this->dir.$dir.'/'); curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'LIST -a'); $list = curl_exec($this->curl); if (curl_errno($this->curl)) { $this->log(curl_error($this->curl)); return false; } $files = explode("\n", $list); // List all the files $i = 0; foreach ($files as $folder) { $struc = []; $current = preg_split("/[\s]+/", $folder, 9); $i = 0; $struc['perms'] = $current[0]; $struc['permsn'] = $this->chmod_num($struc['perms']); $struc['number'] = $current[1]; $struc['owner'] = $current[2]; $struc['group'] = $current[4]; $struc['size'] = $current[(count($current)-5)]; $struc['month'] = $current[(count($current)-4)]; $struc['day'] = $current[(count($current)-3)]; $date = $current[(count($current)-2)]; $struc['name'] = trim(str_replace('//', '', end($current))); if (strlen($date) == 4) { $struc['year'] = $date; $struc['time'] = '00:00'; } else { $struc['year'] = date('Y'); if (strtotime($struc['month'].' '.$struc['day']) > time()) { $struc['year']--; } $struc['time'] = $date; } $struc['modified'] = strtotime($struc['month'].' '.$struc['day'].' '.$struc['year'].' '.$struc['time']); $struc['raw'] = $folder; if (substr($folder, 0, 1) == "d") { $struc['type'] = 'folder'; } elseif (substr($folder, 0, 1) == "l") { $struc['type'] = 'link'; if ($pos) { $struc['name'] = substr($struc['name'], 0, $pos); } } else { $struc['type'] = 'file'; } if ($struc['name']) { $items[] = $struc; } $i++; } return $items; } function search_nodes($s, $path) { $packet_handler = function($string) { $items = explode("\n", trim($string)); $items = array_unique($items); sort($items); foreach ($items as $k => $v) { if (strstr($v, ':')) { continue; } $v = substr($v, strlen($dir)); $this->send_msg($this->startedAt, $v); } }; $dir = $this->dir.$path; $this->exec("pkill grep"); $this->exec("pkill find"); $results = $this->exec('grep -Ilr '.escapeshellarg($s).' '.escapeshellarg($dir), $packet_handler); $results .= $this->exec('find '.escapeshellarg($dir).' -name "'.escapeshellarg($s).'*"', $packet_handler); } function search($s, $path) { return $this->search_nodes($s, $path); } function close() { if ($this->curl) { curl_close($this->curl); } if ($this->keyfile) { unlink($this->keyfile); } } function exec($command) { if (!is_array($command)) { $command = array($command); } $this->log($command); // Make curl run our command before the actual operation, ... curl_setopt($this->curl, CURLOPT_QUOTE, $command); // ... but do not do any operation at all curl_setopt($this->curl, CURLOPT_NOBODY, 1); if ($this->debug) { curl_setopt($this->curl, CURLOPT_VERBOSE, true); $stream_log = fopen('php://temp', 'r+b'); curl_setopt($this->curl, CURLOPT_STDERR, $stream_log); } $result = curl_exec($this->curl); $error_no = curl_errno($this->curl); if ($error_no === 0) { return true; } else { if ($this->debug) { //print_r(curl_getinfo($this->curl)); rewind($stream_log); $this->log(stream_get_contents($stream_log)); fclose($stream_log); } else { $this->log(curl_error($this->curl)); } return false; } } } } else { throw new Exception('no sftp library found'); } } else if ($server_type) { throw new Exception('invalid server type: '.$server_type); } else { throw new Exception("missing server type in proxy file"); } /* START OF GIT CLASS */ /** * Git Interface Class * * This class enables the creating, reading, and manipulation * of git repositories. * * @class Git */ class Git { /** * Git executable location * * @var string */ protected static $bin = '/usr/bin/git'; /** * Sets git executable path * * @param string $path executable location */ public static function set_bin($path) { self::$bin = $path; } /** * Gets git executable path */ public static function get_bin() { return self::$bin; } /** * Sets up library for use in a default Windows environment */ public static function windows_mode() { self::set_bin('git'); } /** * Create a new git repository * * Accepts a creation path, and, optionally, a source path * * @access public * @param string repository path * @param string directory to source * @return GitRepo */ public static function &create($repo_path, $source = null) { return GitRepo::create_new($repo_path, $source); } /** * Open an existing git repository * * Accepts a repository path * * @access public * @param string repository path * @return GitRepo */ public static function open($repo_path) { return new GitRepo($repo_path); } /** * Clones a remote repo into a directory and then returns a GitRepo object * for the newly created local repo * * Accepts a creation path and a remote to clone from * * @access public * @param string repository path * @param string remote source * @param string reference path * @return GitRepo **/ public static function &clone_remote($repo_path, $remote, $reference = null) { return GitRepo::create_new($repo_path, $remote, true, $reference); } /** * Checks if a variable is an instance of GitRepo * * Accepts a variable * * @access public * @param mixed variable * @return bool */ public static function is_repo($var) { return (get_class($var) == 'GitRepo'); } } // ------------------------------------------------------------------------ /** * Git Repository Interface Class * * This class enables the creating, reading, and manipulation * of a git repository * * @class GitRepo */ class GitRepo { protected $repo_path = null; protected $bare = false; protected $envopts = []; /** * Create a new git repository * * Accepts a creation path, and, optionally, a source path * * @access public * @param string repository path * @param string directory to source * @param string reference path * @return GitRepo */ public static function &create_new($repo_path, $source = null, $remote_source = false, $reference = null) { if (is_dir($repo_path) && file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { throw new Exception('"'.$repo_path.'" is already a git repository'); } else { $repo = new self($repo_path, true, false); if (is_string($source)) { if ($remote_source) { if (!is_dir($reference) || !is_dir($reference.'/.git')) { throw new Exception('"'.$reference.'" is not a git repository. Cannot use as reference.'); } else if (strlen($reference)) { $reference = realpath($reference); $reference = "--reference $reference"; } $repo->clone_remote($source, $reference); } else { $repo->clone_from($source); } } else { $repo->run('init'); } return $repo; } } /** * Constructor * * Accepts a repository path * * @access public * @param string repository path * @param bool create if not exists? * @return void */ public function __construct($repo_path = null, $create_new = false, $_init = true) { if (is_string($repo_path)) { $this->set_repo_path($repo_path, $create_new, $_init); } } /** * Set the repository's path * * Accepts the repository path * * @access public * @param string repository path * @param bool create if not exists? * @param bool initialize new Git repo if not exists? * @return void */ public function set_repo_path($repo_path, $create_new = false, $_init = true) { if (is_string($repo_path)) { if ($new_path = realpath($repo_path)) { $repo_path = $new_path; if (is_dir($repo_path)) { // Is this a work tree? if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { $this->repo_path = $repo_path; $this->bare = false; if (!is_writable($repo_path."/.git")) { $user_info = posix_getpwuid(fileowner($repo_path."/.git")); $file_owner = $user_info['name']; $this->command_prefix = 'sudo -u '.$file_owner.' '; } // Is this a bare repo? } else if (is_file($repo_path."/config")) { $parse_ini = parse_ini_file($repo_path."/config"); if ($parse_ini['bare']) { $this->repo_path = $repo_path; $this->bare = true; } } else { if ($create_new) { $this->repo_path = $repo_path; if ($_init) { $this->run('init'); } } else { throw new Exception('"'.$repo_path.'" is not a git repository'); } } } else { throw new Exception('"'.$repo_path.'" is not a directory'); } } else { if ($create_new) { if ($parent = realpath(dirname($repo_path))) { mkdir($repo_path); $this->repo_path = $repo_path; if ($_init) $this->run('init'); } else { throw new Exception('cannot create repository in non-existent directory'); } } else { throw new Exception('"'.$repo_path.'" does not exist'); } } } } /** * Get the path to the git repo directory (eg. the ".git" directory) * * @access public * @return string */ public function git_directory_path() { return ($this->bare) ? $this->repo_path : $this->repo_path."/.git"; } /** * Tests if git is installed * * @access public * @return bool */ public function test_git() { $descriptorspec = array( 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ); $pipes = []; $resource = proc_open(Git::get_bin(), $descriptorspec, $pipes); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); foreach ($pipes as $pipe) { fclose($pipe); } $status = trim(proc_close($resource)); return ($status != 127); } /** * Run a command in the git repository * * Accepts a shell command to run * * @access protected * @param string command to run * @return string */ protected function run_command($command) { $descriptorspec = array( 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ); $pipes = []; /* Depending on the value of variables_order, $_ENV may be empty. * In that case, we have to explicitly set the new variables with * putenv, and call proc_open with env=null to inherit the reset * of the system. * * This is kind of crappy because we cannot easily restore just those * variables afterwards. * * If $_ENV is not empty, then we can just copy it and be done with it. */ if (count($_ENV) === 0) { $env = NULL; foreach ($this->envopts as $k => $v) { putenv(sprintf("%s=%s", $k, $v)); } } else { $env = array_merge($_ENV, $this->envopts); } $cwd = $this->repo_path; $resource = proc_open($command, $descriptorspec, $pipes, $cwd, $env); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); foreach ($pipes as $pipe) { fclose($pipe); } $status = trim(proc_close($resource)); if ($status) throw new Exception($stderr); // weird issue where result is in stderr if (!$stdout and $stderr) return $stderr; return $stdout; } /** * Run a git command in the git repository * * Accepts a git command to run * * @access public * @param string command to run * @return string */ public function run($command) { return shell_exec($this->command_prefix.Git::get_bin() . " " . $command . ' 2>&1'); } /** * Runs a 'git status' call * * Accept a convert to HTML bool * * @access public * @param bool return string with
* @return string */ public function status($html = false) { $msg = $this->run("status"); if ($html == true) { $msg = str_replace("\n", "
", $msg); } return $msg; } /** * Runs a `git add` call * * Accepts a list of files to add * * @access public * @param mixed files to add * @return string */ public function add($files = "*") { if (is_array($files)) { $files = '"'.implode('" "', $files).'"'; } return $this->run("add $files -v"); } /** * Runs a `git rm` call * * Accepts a list of files to remove * * @access public * @param mixed files to remove * @param Boolean use the --cached flag? * @return string */ public function rm($files = "*", $cached = false) { if (is_array($files)) { $files = '"'.implode('" "', $files).'"'; } return $this->run("rm ".($cached ? '--cached ' : '').$files); } /** * Runs a `git commit` call * * Accepts a commit message string * * @access public * @param string commit message * @param boolean should all files be committed automatically (-a flag) * @return string */ public function commit($message = "", $commit_all = true) { $flags = $commit_all ? '-av' : '-v'; return $this->run("commit ".$flags." -m ".escapeshellarg($message)); } /** * Runs a `git clone` call to clone the current repository * into a different directory * * Accepts a target directory * * @access public * @param string target directory * @return string */ public function clone_to($target) { return $this->run("clone --local ".$this->repo_path." $target"); } /** * Runs a `git clone` call to clone a different repository * into the current repository * * Accepts a source directory * * @access public * @param string source directory * @return string */ public function clone_from($source) { return $this->run("clone --local $source ".$this->repo_path); } /** * Runs a `git clone` call to clone a remote repository * into the current repository * * Accepts a source url * * @access public * @param string source url * @param string reference path * @return string */ public function clone_remote($source, $reference) { return $this->run("clone $reference $source ".$this->repo_path); } /** * Runs a `git clean` call * * Accepts a remove directories flag * * @access public * @param bool delete directories? * @param bool force clean? * @return string */ public function clean($dirs = false, $force = false) { return $this->run("clean".(($force) ? " -f" : "").(($dirs) ? " -d" : "")); } /** * Runs a `git branch` call * * Accepts a name for the branch * * @access public * @param string branch name * @return string */ public function create_branch($branch) { return $this->run("branch $branch"); } /** * Runs a `git branch -[d|D]` call * * Accepts a name for the branch * * @access public * @param string branch name * @return string */ public function delete_branch($branch, $force = false) { return $this->run("branch ".(($force) ? '-D' : '-d')." $branch"); } /** * Runs a `git branch` call * * @access public * @param bool keep asterisk mark on active branch * @return array */ public function list_branches($keep_asterisk = false) { $branchArray = explode("\n", $this->run("branch")); foreach ($branchArray as $i => &$branch) { $branch = trim($branch); if (! $keep_asterisk) { $branch = str_replace("* ", "", $branch); } if ($branch == "") { unset($branchArray[$i]); } } return $branchArray; } /** * Lists remote branches (using `git branch -r`). * * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). * * @access public * @return array */ public function list_remote_branches() { $branchArray = explode("\n", $this->run("branch -r")); foreach ($branchArray as $i => &$branch) { $branch = trim($branch); if ($branch == "" || strpos($branch, 'HEAD -> ') !== false) { unset($branchArray[$i]); } } return $branchArray; } /** * Returns name of active branch * * @access public * @param bool keep asterisk mark on branch name * @return string */ public function active_branch($keep_asterisk = false) { $branchArray = $this->list_branches(true); $active_branch = preg_grep("/^\*/", $branchArray); reset($active_branch); if ($keep_asterisk) { return current($active_branch); } else { return str_replace("* ", "", current($active_branch)); } } /** * Runs a `git checkout` call * * Accepts a name for the branch * * @access public * @param string branch name * @return string */ public function checkout($branch) { return $this->run('checkout "$branch"'); } /** * Runs a `git merge` call * * Accepts a name for the branch to be merged * * @access public * @param string $branch * @return string */ public function merge($branch) { return $this->run("merge $branch --no-ff"); } /** * Runs a git fetch on the current branch * * @access public * @return string */ public function fetch() { return $this->run("fetch"); } /** * Add a new tag on the current position * * Accepts the name for the tag and the message * * @param string $tag * @param string $message * @return string */ public function add_tag($tag, $message = null) { if ($message === null) { $message = $tag; } return $this->run("tag -a $tag -m " . escapeshellarg($message)); } /** * List all the available repository tags. * * Optionally, accept a shell wildcard pattern and return only tags matching it. * * @access public * @param string $pattern Shell wildcard pattern to match tags against. * @return array Available repository tags. */ public function list_tags($pattern = null) { $tagArray = explode("\n", $this->run("tag -l $pattern")); foreach ($tagArray as $i => &$tag) { $tag = trim($tag); if ($tag == '') { unset($tagArray[$i]); } } return $tagArray; } /** * Push specific branch to a remote * * Accepts the name of the remote and local branch * * @param string $remote * @param string $branch * @return string */ public function push($remote, $branch) { return $this->run("push --tags $remote $branch"); } /** * Pull specific branch from remote * * Accepts the name of the remote and local branch * * @param string $remote * @param string $branch * @return string */ public function pull($remote, $branch) { return $this->run("pull $remote $branch"); } /** * List log entries. * * @param strgin $format * @return string */ public function log($format = null) { if ($format === null) return $this->run('log'); else return $this->run('log --pretty=format:"' . $format . '"'); } /** * Sets the project description. * * @param string $new */ public function set_description($new) { $path = $this->git_directory_path(); file_put_contents($path."/description", $new); } /** * Gets the project description. * * @return string */ public function get_description() { $path = $this->git_directory_path(); return file_get_contents($path."/description"); } /** * Sets custom environment options for calling Git * * @param string key * @param string value */ public function setenv($key, $value) { $this->envopts[$key] = $value; } } /* END OF GIT CLASS */ function basename_safe($path) { if (mb_strrpos($path, '/') !== false) { return mb_substr($path, mb_strrpos($path, '/')+1); } else { return $path; } } function file_ext($file) { $tmp = explode('.', $file); return strtolower(end($tmp)); } function so($a, $b) //sort files { if ($a['leaf'] == $b['leaf']) { return (strcasecmp($a['text'], $b['text'])); } else { return $a['leaf']; } } function get_nodes($path, $paths) { global $server; if (!$paths) { $paths = []; } $list = $server->parse_raw_list($path); if ($list === false) { return false; } $files = []; $i = 0; foreach ($list as $v) { $name = basename_safe($v['name']); if ($v['type'] != 'file') { if ($v['name'] == '.' or $v['name'] == '..' or $v['name'] == '.svn' or $v['name'] == '') { continue; } $files[$i] = array( 'id' => (string)$path.$name, 'text' => (string)$name, 'type' => 'folder', 'children' => true, 'data' => array( 'perms' => $v['permsn'], 'modified' => $v['modified'], 'size' => -1 ) ); // which paths to preload $subdir = $path.$v['name'].'/'; $expand = false; foreach ($paths as $p) { if (substr($p, 0, strlen($path.$v['name'])+1) == $path.$v['name'].'/') { $expand = true; break; } } if ($expand) { $files[$i]['state']['opened'] = true; $files[$i]['children'] = get_nodes($path.$v['name'].'/', $paths); } } else { $ext = file_ext(basename_safe($v['name'])); if ($ext == 'lck') { continue; } $files[$i] = array( 'id' => (string)$path.$name, 'text' => (string)$name, 'type' => 'file', 'children' => false, 'data' => array( 'perms' => $v['permsn'], 'modified' => $v['modified'], 'size' => (int)$v['size'] ) ); } $i++; } usort($files, 'so'); return $files; } function get_paths($path) { global $server, $size, $max_size; $list = $server->parse_raw_list($path); if ($list === false) { return false; } $items = []; foreach ($list as $v) { if ($v['type'] != 'file') { if ($v['name'] == '.' or $v['name'] == '..') { continue; } $size += $v['size']; if ($size > $max_size) { return false; } $arr = get_paths($path.$v['name'].'/'); $items = array_merge($items, $arr); } else { $items[] = $path.$v['name']; } } return $items; } function list_nodes($path) { global $server, $server_src, $id; $list = $server_src->parse_raw_list($path); if (!$list) { return []; } $items = []; foreach ($list as $v) { if ($v['name'] == '') { continue; } if ($v['type'] != 'file') { if ($v['name'] == '.' or $v['name'] == '..' or $v['name'] == '.svn') { continue; } $items[] = array( 'path' => $path.$v['name'], 'isDir' => true ); $arr = list_nodes($path.$v['name'].'/', $dest.'/'.$v['name']); $items = array_merge($items, $arr); } else { $items[] = array( 'path' => $path.$v['name'], 'isDir' => false ); } } return $items; } function copy_nodes($path, $dest) { global $server, $server_src, $id; $list = $server_src->parse_raw_list($path); if ($list === false) { return false; } $server->mkdir($dest); $i = 0; foreach ($list as $v) { if ($v['type'] != 'file') { if ($v['name'] == '.' or $v['name'] == '..' or $v['name'] == '.svn') { continue; } copy_nodes($path.$v['name'].'/', $dest.'/'.$v['name']); } else { $content = $server_src->get($path.'/'.$v['name']); $server->put($dest.'/'.$v['name'], $content); } $i++; } } if ($_POST['path'] == 'root') { $_POST['path'] = ''; } $site = $_GET['site']; if ($_POST['server_type']) { $options = array( 'site' => $_POST ); } switch ($server_type) { case 'ftp': $server = new ftp(); $result = $server->connect($host, $username, $password, $port, $dir, array('pasv' => $pasv)); if ($result === false) { $response['error'] = end($server->ftp_log); echo json_encode($response); exit; } break; case 'sftp': $server = new sftp(); $result = $server->connect($host, $username, $password, $port, $dir); if ($result === false) { $response['error'] = end($server->ftp_log); echo json_encode($response); exit; } break; default: $server = new local(); break; } if ($_GET['cmd']) { $_POST['cmd'] = $_GET['cmd']; } $response = []; switch ($_POST['cmd']) { case 'test': $files = $server->parse_raw_list('/'); if ($files === false) { $response['error'] = 'Dir listing failed'; } break; case 'save': if ($server->put($_POST['file'], $_POST['content']) !== false) { $response['last_modified'] = $server->last_modified($_POST['file']); } else { $response['error'] = 'Failed saving '.$_POST['file']; } if (file_ext($_POST['file']) == 'less' and file_exists('shiftedit-lessc.inc.php')) { require_once('shiftedit-lessc.inc.php'); $less = new lessc; $_POST['content'] = $less->compile($_POST['content']); $file = substr($_POST['file'], 0, -5).'.css'; $server->put($file, $_POST['content'], $_POST['compileId'], $_POST['parent']); } elseif (file_ext($_POST['file']) == 'scss' and file_exists('shiftedit-scss.inc.php')) { require_once('shiftedit-scss.inc.php'); $scss = new scssc(); $scss->setImportPaths(dirname($_POST['file'])); $_POST['content'] = $scss->compile($_POST['content']); $file = substr($_POST['file'], 0, -5).'.css'; $server->put($file, $_POST['content'], $_POST['compileId'], $_POST['parent']); $response['file_id'] = $_POST['file']; } break; case 'open': $response['content'] = $server->get($_POST['file']); break; case 'search': ob_end_clean(); header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); //print_r(ob_get_status()); $server->search($_GET['s'], $_GET['path']); $server->close(); exit; break; case 'get': case 'list': session_write_close(); if ($_POST['path'] and substr($_POST['path'], -1) !== '/') { $_POST['path'] .= '/'; } $response['files'] = []; if ($_POST['path'] == '/') { $_POST['path'] = ''; } if ($_POST['path'] == '' and $_GET['path']) { //used by save as $response['files'] = get_nodes($_GET['path'], array(dirname($_GET['path']).'/')); } else { //preload paths $response['files'] = get_nodes($_POST['path'], $_SESSION['paths']); } if ($response['files'] === false) { $response['error'] = 'Error getting files: '.end($server->ftp_log); } break; case 'list_all': if ($_POST['path'] and substr($_POST['path'], -1) !== '/') { $_POST['path'] .= '/'; } $server_src = $server; $response['files'] = list_nodes($_POST['path']); break; case 'file_exists': $response['file_exists'] = $server->file_exists($_GET['file']); break; case 'rename': $old_name = $_POST['oldname']; $new_name = $_POST['newname']; if (!$server->rename($old_name, $new_name)) { $response['error'] = 'Cannot rename file'; } break; case 'newdir': $dir = $_POST['dir']; if (!$server->mkdir($dir)) { $response['error'] = 'Cannot create dir'; } break; case 'newfile': $content = ''; if (!$server->put($_POST['file'], $content)) { $response['error'] = 'Cannot create file'; } break; case 'duplicate': case 'paste': if (!$_POST['dest'] or !$_POST['path']) { $response['error'] = 'Cannot create file'; } else { $server_src = $server; if ($_POST['isDir']) { if ($_POST['dest'] and $server->file_exists($_POST['dest'])) {} elseif ($_POST['dest'] and $server->mkdir($_POST['dest'])) {} else { $response['error'] = 'Cannot create folder: '.$_POST['dest']; } } else { $content = $server_src->get($_POST['path']); if ($content === false) { $response['error'] = 'Cannot read file: '.$_POST['path']; } elseif (!$_POST['dest'] or !$server->put($_POST['dest'], $content)) { $response['error'] = 'Cannot create file: ' . $_POST['dest']; } } if ($_POST['path'] and $_POST['cut'] == 'true') { $server_src->delete($_POST['path']); } } break; case 'delete': $files = []; // backcompat if ($_GET['file']) { header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $server->startedAt = time(); $files[] = $_GET['file']; } else if ($_POST['files']) { if (count($_POST['files']) === 1) { $files = $_POST['files']; } else { $_SESSION['del_queue'] = $_POST['files']; $response['queue'] = 1; } } else if ($_GET['queue']) { $files = $_SESSION['del_queue']; unset($_SESSION['del_queue']); if (!$files) { $response['error'] = 'No files to delete'; } else { header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $server->startedAt = time(); } } // delete files foreach ($files as $file) { if (!$server->is_dir($file)) { if (!$server->delete($file)) { $response['error'] = 'Cannot delete file: '.end($server->ftp_log); } } else { if (!$server->delete($file)) { $response['error'] = 'Cannot delete directory: '.end($server->ftp_log); } } } break; case 'upload': $response = []; if ($_POST['chunked']) { $relative_path = $_POST['resumableRelativePath'] ?: $_POST['resumableFilename']; if (!$relative_path) { throw new Exception('Cannot find file path'); } if (substr($relative_path, 0, 1) != '/') { $relative_path = '/' . $relative_path; } $path = $_POST['path'] . $relative_path; // check dir $dir = dirname($path); if ($dir && !$server->file_exists($dir)) { if (false === $server->mkdir_recursive($dir)) { throw new Exception('Cannot mkdir ' . $dir); } } $resume_pos = ($_POST['resumableChunkNumber']-1) * $_POST['resumableChunkSize']; $content = file_get_contents($_FILES['file']['tmp_name']); if (!$server->put($path, $content, $resume_pos)) { $response['error'] = 'Cannot save file '.$path; } } elseif (isset($_POST['file']) and isset($_POST['content'])) { $content = $_POST['content']; if (substr($content, 0, 5) == 'data:') { $pos = strpos($content, 'base64'); if ($pos) { $content = base64_decode(substr($content, $pos+6)); } } if (strstr($_POST['file'], '.')) { if (!$server->put($_POST['file'], $content)) { $response['error'] = 'Can\'t create file '.$file['name']; } } else { if (!$server->mkdir($_POST['file'])) { $response['error'] = 'Can\'t create folder '.$file['name']; } } } else { foreach ($_FILES as $key => $file) { if ($file['error'] == UPLOAD_ERR_OK) { $content = file_get_contents($file['tmp_name']); if (!$server->put($_POST['path'].'/'.$file['name'], $content)) { $response['error'] = 'Can\'t create file '.$file['name']; } } else { $response['error'] = $error.' '.$file['name']; } } } break; case 'download': $response['content'] = base64_encode($server->get($_GET['file'])); break; case 'chmod': $file = $_GET['file']; if (!$server->chmod($_GET['mode'], $file)) { $response['error'] = 'Cannot chmod file'; } break; case 'uploadByURL': if (substr($_POST['url'], 0, 7) != 'http://' && substr($_POST['url'], 0, 8) != 'https://') { $response['error'] = 'Invalid URL'; } else { $content = file_get_contents($_POST['url']); $file = basename_safe($_POST['url']); if (!$file) { $response['error'] = 'Missing file name'; } else { if (!$server->put($_POST['path'].'/'.$file, $content)) { $response['error'] = 'Can\'t save file'; } } } break; case 'saveByURL': if (substr($_POST['url'], 0, 7) != 'http://' && substr($_POST['url'], 0, 8) != 'https://') { $response['error'] = 'Invalid URL'; } else { $content = file_get_contents($_POST['url']); if ($server->put($_POST['path'], $content)) { //success } else { $response['error'] = 'Can\'t save file'; } } break; case 'extract': header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $startedAt = time(); $file = $server->get($_GET['file'], true); $pos = strpos($_GET['file'], '.'); $file_ext = substr($_GET['file'], $pos); $tmpfname = tmpfile(); $handle = fopen($tmpfname, "w"); fwrite($handle, $data); fclose($handle); $za = new ZipArchive(); $za->open($file); $server->send_msg($startedAt, $za->numFiles); $complete = 0; for ($i = 0; $i < $za->numFiles; $i++) { $entry = $za->statIndex($i); $server->send_msg($startedAt, $entry['name']); if (substr($entry['name'], -1) == '/') { $server->mkdir(dirname($_GET['file']).'/'.$entry['name']); } else { $server->put(dirname($_GET['file']).'/'.$entry['name'], $za->getFromIndex($i)); } $complete++; } unlink($tmpfname); break; case 'compress': if ($_POST['paths']) { $_SESSION['paths'] = $_POST['paths']; } else { if ($_GET['d']) { if ($_SESSION['download']['name']) { header("Content-Disposition: attachment; filename=" . $_SESSION['download']['name']); header("Content-Type: application/octet-stream"); print file_get_contents($_SESSION['download']['file']); unlink($_SESSION['download']['file']); unset($_SESSION['download']); unset($_SESSION['paths']); exit; } else { throw new Exception('no zip file'); } } header('Content-Type: text/event-stream'); $size = 0; $max_size = 10000000; $id = time(); $server->send_msg($id, 'Initializing'); $tmpdir = sys_get_temp_dir() or die('failed to get tmp dir'); $zip_file = tempnam($tmpdir, "shiftedit_zip_") or die('failed to create tmp file'); $zip = new ZipArchive(); if ($zip->open($zip_file, ZipArchive::CREATE) !== TRUE) { throw new Exception("cannot open <$zip_file>\n"); } $paths = $_SESSION['paths']; foreach ($paths as $file) { $is_dir = $server->is_dir($file); if (!$is_dir) { $files = [$file]; if ($server->size($file) > $max_size) { $server->send_msg($id, 'File size limit exceeded '.$file); } } else { $files = get_paths($file.'/'); if ($files === false) { $server->send_msg($id, 'Error getting files'); exit; } $zip->addEmptyDir($file); } foreach ($files as $file) { $server->send_msg($id, 'Compressing '.$file); $dir = dirname($file); $zip->addEmptyDir($dir); $content = $server->get($file); if ($content !== false) { $zip->addFromString($file, $content); } } } $zip->close(); $zip_name = (count($paths) === 1) ? basename($paths[0]) : 'files'; $_SESSION['download'] = array( 'name' => $zip_name.'.zip', 'file' => $zip_file ); $server->send_msg($id, 'done'); } break; case 'definitions': $json = file_get_contents($definitions); $response['definitions'] = json_decode($json); break; case 'save_path': if ($_GET['path'] and substr($_GET['path'], -1) !== '/') { $_GET['path'] .= '/'; } if ($_GET['expand']) { $_SESSION['paths'][] = $_GET['path']; $_SESSION['paths'] = array_unique($_SESSION['paths']); } else { foreach ($_SESSION['paths'] as $k => $v) { if (substr($v, 0, strlen($_GET['path'])) == $_GET['path']) { unset($_SESSION['paths'][$k]); } } } break; case 'git_info': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') // user info $raw = $git->run('config user.name'); $response['config']['name'] = trim($raw); $raw = $git->run('config user.email'); $response['config']['email'] = trim($raw); // commit history $raw = $git->run('log --pretty=format:"%H|%an|%ar|%s" --max-count=20'); $lines = explode("\n", trim($raw)); $response['commits'] = []; foreach ($lines as $line) { $arr = explode('|', $line); $response['commits'][] = array( 'hash' => $arr[0], 'author' => $arr[1], 'date' => $arr[2], 'subject' => $arr[3], ); } // stashes $raw = $git->run("stash list"); $lines = explode("\n", trim($raw)); $response['stashes'] = []; foreach ($lines as $v) { preg_match('/stash@{([0-9]+)}: (.*)/', $v, $matches); $response['stashes'][] = array( 'index' => $matches[1], 'name' => $matches[2], ); } // branches $raw = $git->run("branch -a"); if ($raw) { $lines = explode("\n", trim($raw)); $branches = []; foreach ($lines as $v) { $selected = (substr($v, 0, 1) === '*'); $branch = substr($v, 0, 1) === '*' ? substr($v, 2) : trim($v); $remote = substr($branch, 0, 8) === 'remotes/'; $branch = str_replace('remotes/origin/', '', $branch); if (!$branches[$branch]) { $branches[$branch] = [ 'name' => $branch, 'selected' => $selected, 'remote' => $remote, ]; } ksort($branches); } $response['branches'] = array_values($branches); } // status $raw = $git->run('status -bs'); $raw = rtrim($raw); if ($raw) { $lines = explode("\n", $raw); $response['status'] = array_shift($lines); $response['changes'] = []; foreach ($lines as $line) { $path = substr($line, 3); $response['changes'][] = array( 'path' => $path, 'status' => substr($line, 0, 2), ); } } break; case 'config': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['name']) and trim($_GET['email'])) { $response['data'][] = $git->run('config user.name "'.trim($_GET['name']).'"'); $response['data'][] = $git->run('config user.email "'.trim($_GET['email']).'"'); } break; case 'clone': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['url'])) { $response['data'] = $git->run("clone ".$_GET['url'].' .'); if (trim($_GET['name']) and trim($_GET['email'])) { $git->run('config user.name "'.trim($_GET['name']).'"'); $git->run('config user.email "'.trim($_GET['email']).'"'); } } break; case 'checkout': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['branch'])) { $response['data'] = $git->run('checkout -q "' . $_GET['branch'] . '"'); if (substr($response['data'], 0, 5) === 'error') { $response['error'] = $response['data']; } } break; case 'discard': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['path'])) { $raw = $git->run('status -s'); $lines = explode("\n", rtrim($raw)); foreach ($lines as $line) { $path = substr($line, 3); $status = substr($line, 0, 1); if ($path === $_GET['path']) { try { if ($status === '?' or $status === 'A') { if ($status === 'A') { $response['data'] = $git->run("reset ".$_GET['path']); } $response['data'] = $git->run("clean -f ".$_GET['path']); } else { $response['data'] = $git->run("checkout -- ".$_GET['path']); } } catch (exception $e) { $response['error'] = $e->getMessage(); break 2; } break; } } } break; case 'ignore': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $raw = $git->run('rm --cached ' . $_GET['path']); $content = $server->get('.gitignore'); if ($content) { $content .= "\n"; } $content .= $_GET['path']; $result = $server->put('.gitignore', $content); break; case 'create_branch': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['name']) and trim($_GET['from'])) { $raw = $git->run('checkout -b "'.$_GET['name'] . '" "' . $_GET['from'] . '"'); $raw = $git->run('push -u origin ' . $_GET['name']); } break; case 'delete_branch': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['branch'])) { $git->run("checkout -q master"); if ($_GET['force']) { $raw = $git->run('branch -D "' . $_GET['branch'] . '"'); } else { $raw = $git->run('branch -d "' . $_GET['branch'] . '"'); } $response['result'] = $git->run('push origin --delete "' . $_GET['branch'] . '"'); } break; case 'merge': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['branch'])) { $response['result'] = $git->run('merge "' . $_GET['branch'] . '"'); } break; case 'revert': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['hash'])) { $response['result'] = $git->run('revert -m 1 ' . $_GET['hash']); } break; case 'reset': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (trim($_GET['hash'])) { $response['result'] = $git->run('reset --hard ' . $_GET['hash']); } break; case 'commit': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if (count((array)$_POST['paths']) and $_POST['subject']) { foreach ($_POST['paths'] as $path) { $git->run("add ".$path); } $response['result'] = $git->run('commit -m "'.addslashes($_POST['subject']).'" -m "'.addslashes($_POST['description']).'"'); } break; case 'diff': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if ($_GET['path']) { $response['result'] = $git->run('--no-pager diff -- ' . escapeshellarg($_GET['path'])); if (!$response['result']) { $response['result'] = $git->run('--no-pager diff -- /dev/null ' . escapeshellarg($_GET['path'])); } } break; case 'show': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') if ($_GET['commit']) { $response['result'] = $git->run('--no-pager show '.$_GET['commit']); } break; case 'stash_push': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $response['result'] = $git->run('stash push ' . ($_POST['subject'] ? '-m "'.addslashes($_POST['subject']).'"' : '')); break; case 'stash_show': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $response['result'] = $git->run('--no-pager stash show -p stash@{' . (int)$_GET['index'] . '}'); break; case 'stash_apply': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $response['result'] = $git->run('stash apply stash@{' . (int)$_GET['index'] . '}'); break; case 'stash_drop': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $response['result'] = $git->run('stash drop stash@{' . (int)$_GET['index'] . '}'); break; case 'sync': $git = Git::open(dirname(__FILE__)); // -or- Git::create('/path/to/repo') $response['result'] = $git->run('pull'); $response['result'] = $git->run('push'); break; default: $response['error'] = 'No command'; break; } if ($server) { $server->close(); } } catch (Exception $e) { $response['error'] = $e->getMessage(); } $response['success'] = ($response['error']) ? false : true; print json_encode($response);