mirror of
https://github.com/xevidos/codiad.git
synced 2024-11-10 21:26:35 +01:00
630 lines
22 KiB
PHP
Executable File
630 lines
22 KiB
PHP
Executable File
<?php
|
|
|
|
/*
|
|
* Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
|
|
* as-is and without warranty under the MIT License. See
|
|
* [root]/license.txt for more. This information must remain intact.
|
|
*/
|
|
|
|
require_once('../../lib/diff_match_patch.php');
|
|
require_once('../../common.php');
|
|
|
|
class Filemanager extends Common
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// PROPERTIES
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public $root = "";
|
|
public $project = "";
|
|
public $rel_path = "";
|
|
public $path = "";
|
|
public $patch = "";
|
|
public $type = "";
|
|
public $new_name = "";
|
|
public $content = "";
|
|
public $destination = "";
|
|
public $upload = "";
|
|
public $controller = "";
|
|
public $upload_json = "";
|
|
public $search_string = "";
|
|
|
|
public $search_file_type = "";
|
|
public $query = "";
|
|
public $foptions = "";
|
|
|
|
// JSEND Return Contents
|
|
public $status = "";
|
|
public $data = "";
|
|
public $message = "";
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// METHODS
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
// -----------------------------||----------------------------- //
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Construct
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function __construct($get, $post, $files)
|
|
{
|
|
$this->rel_path = Filemanager::cleanPath($get['path']);
|
|
|
|
if ($this->rel_path!="/") {
|
|
$this->rel_path .= "/";
|
|
}
|
|
if (!empty($get['query'])) {
|
|
$this->query = $get['query'];
|
|
}
|
|
if (!empty($get['options'])) {
|
|
$this->foptions = $get['options'];
|
|
}
|
|
$this->root = $get['root'];
|
|
if ($this->isAbsPath($get['path'])) {
|
|
$this->path = Filemanager::cleanPath($get['path']);
|
|
} else {
|
|
$this->root .= '/';
|
|
$this->path = $this->root . Filemanager::cleanPath($get['path']);
|
|
}
|
|
// Search
|
|
if (!empty($post['search_string'])) {
|
|
$this->search_string = ($post['search_string']);
|
|
}
|
|
if (!empty($post['search_file_type'])) {
|
|
$this->search_file_type = ($post['search_file_type']);
|
|
}
|
|
// Create
|
|
if (!empty($get['type'])) {
|
|
$this->type = $get['type'];
|
|
}
|
|
// Modify\Create
|
|
if (!empty($get['new_name'])) {
|
|
$this->new_name = $get['new_name'];
|
|
}
|
|
|
|
foreach (array('content', 'mtime', 'patch') as $key) {
|
|
if (!empty($post[$key])) {
|
|
if (get_magic_quotes_gpc()) {
|
|
$this->$key = stripslashes($post[$key]);
|
|
} else {
|
|
$this->$key = $post[$key];
|
|
}
|
|
}
|
|
}
|
|
// Duplicate
|
|
if (!empty($get['destination'])) {
|
|
$get['destination'] = Filemanager::cleanPath($get['destination']);
|
|
if ($this->isAbsPath($get['path'])) {
|
|
$this->destination = $get['destination'];
|
|
} else {
|
|
$this->destination = $this->root . $get['destination'];
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// INDEX (Returns list of files and directories)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function index()
|
|
{
|
|
|
|
if (file_exists($this->path)) {
|
|
$index = array();
|
|
if (is_dir($this->path) && $handle = opendir($this->path)) {
|
|
while (false !== ($object = readdir($handle))) {
|
|
if ($object != "." && $object != ".." && $object != $this->controller) {
|
|
if (is_dir($this->path.'/'.$object)) {
|
|
$type = "directory";
|
|
$size=count(glob($this->path.'/'.$object.'/*'));
|
|
} else {
|
|
$type = "file";
|
|
$size=@filesize($this->path.'/'.$object);
|
|
}
|
|
$index[] = array(
|
|
"name"=>$this->rel_path . $object,
|
|
"type"=>$type,
|
|
"size"=>$size
|
|
);
|
|
}
|
|
}
|
|
|
|
$folders = array();
|
|
$files = array();
|
|
foreach ($index as $item => $data) {
|
|
if ($data['type']=='directory') {
|
|
$folders[] = array("name"=>$data['name'],"type"=>$data['type'],"size"=>$data['size']);
|
|
}
|
|
if ($data['type']=='file') {
|
|
$files[] = array("name"=>$data['name'],"type"=>$data['type'],"size"=>$data['size']);
|
|
}
|
|
}
|
|
|
|
function sorter($a, $b, $key = 'name')
|
|
{
|
|
return strnatcmp($a[$key], $b[$key]);
|
|
}
|
|
|
|
usort($folders, "sorter");
|
|
usort($files, "sorter");
|
|
|
|
$output = array_merge($folders, $files);
|
|
|
|
$this->status = "success";
|
|
$this->data = '"index":' . json_encode($output);
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Not A Directory";
|
|
}
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Path Does Not Exist";
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
public function find()
|
|
{
|
|
if (!function_exists('shell_exec')) {
|
|
$this->status = "error";
|
|
$this->message = "Shell_exec() Command Not Enabled.";
|
|
} else {
|
|
chdir($this->path);
|
|
$input = str_replace('"', '', $this->query);
|
|
$cmd = 'find -L ';
|
|
$strategy = '';
|
|
if ($this->foptions && $this->foptions['strategy']) {
|
|
$strategy = $this->foptions['strategy'];
|
|
}
|
|
switch ($strategy) {
|
|
case 'substring':
|
|
$cmd = "$cmd -iname ".escapeshellarg('*'.$input.'*');
|
|
break;
|
|
case 'regexp':
|
|
$cmd = "$cmd -regex ".escapeshellarg($input);
|
|
break;
|
|
case 'left_prefix':
|
|
default:
|
|
$cmd = "$cmd -iname ".escapeshellarg($input.'*');
|
|
break;
|
|
}
|
|
$cmd = "$cmd -printf \"%h/%f %y\n\"";
|
|
$output = shell_exec($cmd);
|
|
$file_arr = explode("\n", $output);
|
|
$output_arr = array();
|
|
|
|
error_reporting(0);
|
|
|
|
foreach ($file_arr as $i => $fentry) {
|
|
$farr = explode(" ", $fentry);
|
|
$fname = trim($farr[0]);
|
|
if ($farr[1] == 'f') {
|
|
$ftype = 'file';
|
|
} else {
|
|
$ftype = 'directory';
|
|
}
|
|
if (strlen($fname) != 0) {
|
|
$fname = $this->rel_path . substr($fname, 2);
|
|
$f = array('path' => $fname, 'type' => $ftype );
|
|
array_push($output_arr, $f);
|
|
}
|
|
}
|
|
|
|
if (count($output_arr)==0) {
|
|
$this->status = "error";
|
|
$this->message = "No Results Returned";
|
|
} else {
|
|
$this->status = "success";
|
|
$this->data = '"index":' . json_encode($output_arr);
|
|
}
|
|
}
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// SEARCH
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function search()
|
|
{
|
|
if (!function_exists('shell_exec')) {
|
|
$this->status = "error";
|
|
$this->message = "Shell_exec() Command Not Enabled.";
|
|
} else {
|
|
if ($_GET['type'] == 1) {
|
|
$this->path = WORKSPACE;
|
|
}
|
|
$return = array();
|
|
|
|
$input = str_replace('"', '', $this->search_string);
|
|
$cmd = 'find -L ' . escapeshellarg($this->path) . ' -iregex '.escapeshellarg('.*' . $this->search_file_type ).' -type f | xargs grep -i -I -n -R -H ' . escapeshellarg($input) . '';
|
|
$output = shell_exec($cmd);
|
|
$output_arr = explode("\n", $output);
|
|
foreach ($output_arr as $line) {
|
|
$data = explode(":", $line);
|
|
$da = array();
|
|
if (count($data) > 2) {
|
|
$da['line'] = $data[1];
|
|
$da['file'] = str_replace($this->path, '', $data[0]);
|
|
$da['result'] = str_replace($this->root, '', $data[0]);
|
|
$da['string'] = str_replace($data[0] . ":" . $data[1] . ':', '', $line);
|
|
$return[] = $da;
|
|
}
|
|
}
|
|
if (count($return)==0) {
|
|
$this->status = "error";
|
|
$this->message = "No Results Returned";
|
|
} else {
|
|
$this->status = "success";
|
|
$this->data = '"index":' . json_encode($return);
|
|
}
|
|
}
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// OPEN (Returns the contents of a file)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function open()
|
|
{
|
|
if (is_file($this->path)) {
|
|
$output = file_get_contents($this->path);
|
|
|
|
if (extension_loaded('mbstring')) {
|
|
if (!mb_check_encoding($output, 'UTF-8')) {
|
|
if (mb_check_encoding($output, 'ISO-8859-1')) {
|
|
$output = utf8_encode($output);
|
|
} else {
|
|
$output = mb_convert_encoding($content, 'UTF-8');
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->status = "success";
|
|
$this->data = '"content":' . json_encode($output);
|
|
$mtime = filemtime($this->path);
|
|
$this->data .= ', "mtime":'.$mtime;
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Not A File :".$this->path;
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// OPEN IN BROWSER (Return URL)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function openinbrowser()
|
|
{
|
|
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
|
|
$domainName = $_SERVER['HTTP_HOST'];
|
|
$url = $protocol.WSURL.'/'.$this->rel_path;
|
|
$this->status = "success";
|
|
$this->data = '"url":' . json_encode(rtrim($url, "/"));
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// CREATE (Creates a new file or directory)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function create()
|
|
{
|
|
|
|
// Create file
|
|
if ($this->type=="file") {
|
|
if (!file_exists($this->path)) {
|
|
if ($file = fopen($this->path, 'w')) {
|
|
// Write content
|
|
if ($this->content) {
|
|
fwrite($file, $this->content);
|
|
}
|
|
$this->data = '"mtime":'.filemtime($this->path);
|
|
fclose($file);
|
|
$this->status = "success";
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Cannot Create File";
|
|
}
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "File Already Exists";
|
|
}
|
|
}
|
|
|
|
// Create directory
|
|
if ($this->type=="directory") {
|
|
if (!is_dir($this->path)) {
|
|
mkdir($this->path);
|
|
$this->status = "success";
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Directory Already Exists";
|
|
}
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// DELETE (Deletes a file or directory (+contents or only contents))
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function delete( $keep_parent=false )
|
|
{
|
|
|
|
function rrmdir($path, $follow)
|
|
{
|
|
if (is_file($path)) {
|
|
unlink($path);
|
|
} else {
|
|
$files = array_diff(scandir($path), array('.','..'));
|
|
foreach ($files as $file) {
|
|
if (is_link("$path/$file")) {
|
|
if ($follow) {
|
|
rrmdir("$path/$file", $follow);
|
|
}
|
|
unlink("$path/$file");
|
|
} elseif (is_dir("$path/$file")) {
|
|
rrmdir("$path/$file", $follow);
|
|
} else {
|
|
unlink("$path/$file");
|
|
}
|
|
}
|
|
if( $keep_parent === false ) {
|
|
return rmdir($path);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (file_exists($this->path)) {
|
|
if (isset($_GET['follow'])) {
|
|
rrmdir($this->path, true);
|
|
} else {
|
|
rrmdir($this->path, false);
|
|
}
|
|
$this->status = "success";
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Path Does Not Exist ";
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// MODIFY (Modifies a file name/contents or directory name)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function modify()
|
|
{
|
|
|
|
// Change name
|
|
if ($this->new_name) {
|
|
$explode = explode('/', $this->path);
|
|
array_pop($explode);
|
|
$new_path = implode("/", $explode) . "/" . $this->new_name;
|
|
if (!file_exists($new_path)) {
|
|
if (rename($this->path, $new_path)) {
|
|
//unlink($this->path);
|
|
$this->status = "success";
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Could Not Rename";
|
|
}
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Path Already Exists";
|
|
}
|
|
} else {
|
|
// Change content
|
|
if ($this->content || $this->patch) {
|
|
if ($this->content==' ') {
|
|
$this->content=''; // Blank out file
|
|
}
|
|
if ($this->patch && ! $this->mtime) {
|
|
$this->status = "error";
|
|
$this->message = "mtime parameter not found";
|
|
$this->respond();
|
|
return;
|
|
}
|
|
if (is_file($this->path)) {
|
|
$serverMTime = filemtime($this->path);
|
|
$fileContents = file_get_contents($this->path);
|
|
|
|
if ($this->patch && $this->mtime != $serverMTime) {
|
|
$this->status = "error";
|
|
$this->message = "Client is out of sync";
|
|
//DEBUG : file_put_contents($this->path.".conflict", "SERVER MTIME :".$serverMTime.", CLIENT MTIME :".$this->mtime);
|
|
$this->respond();
|
|
return;
|
|
} elseif (strlen(trim($this->patch)) == 0 && ! $this->content) {
|
|
// Do nothing if the patch is empty and there is no content
|
|
$this->status = "success";
|
|
$this->data = '"mtime":'.$serverMTime;
|
|
$this->respond();
|
|
return;
|
|
}
|
|
|
|
if ($file = fopen($this->path, 'w')) {
|
|
if ($this->patch) {
|
|
$dmp = new diff_match_patch();
|
|
$p = $dmp->patch_apply($dmp->patch_fromText($this->patch), $fileContents);
|
|
$this->content = $p[0];
|
|
//DEBUG : file_put_contents($this->path.".orig",$fileContents );
|
|
//DEBUG : file_put_contents($this->path.".patch", $this->patch);
|
|
}
|
|
|
|
if (fwrite($file, $this->content) === false) {
|
|
$this->status = "error";
|
|
$this->message = "could not write to file";
|
|
} else {
|
|
// Unless stat cache is cleared the pre-cached mtime will be
|
|
// returned instead of new modification time after editing
|
|
// the file.
|
|
clearstatcache();
|
|
$this->data = '"mtime":'.filemtime($this->path);
|
|
$this->status = "success";
|
|
}
|
|
|
|
fclose($file);
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Cannot Write to File";
|
|
}
|
|
} else {
|
|
$this->status = "error";
|
|
$this->message = "Not A File";
|
|
}
|
|
} else {
|
|
$file = fopen($this->path, 'w');
|
|
fclose($file);
|
|
$this->data = '"mtime":'.filemtime($this->path);
|
|
$this->status = "success";
|
|
}
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// DUPLICATE (Creates a duplicate of the object - (cut/copy/paste)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function duplicate()
|
|
{
|
|
|
|
if (!file_exists($this->path)) {
|
|
$this->status = "error";
|
|
$this->message = "Invalid Source";
|
|
}
|
|
|
|
function recurse_copy($src, $dst)
|
|
{
|
|
$dir = opendir($src);
|
|
@mkdir($dst);
|
|
while (false !== ( $file = readdir($dir))) {
|
|
if (( $file != '.' ) && ( $file != '..' )) {
|
|
if (is_dir($src . '/' . $file)) {
|
|
recurse_copy($src . '/' . $file, $dst . '/' . $file);
|
|
} else {
|
|
copy($src . '/' . $file, $dst . '/' . $file);
|
|
}
|
|
}
|
|
}
|
|
closedir($dir);
|
|
}
|
|
|
|
if ($this->status!="error") {
|
|
if (is_file($this->path)) {
|
|
copy($this->path, $this->destination);
|
|
$this->status = "success";
|
|
} else {
|
|
recurse_copy($this->path, $this->destination);
|
|
if (!$this->response) {
|
|
$this->status = "success";
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// UPLOAD (Handles uploads to the specified directory)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function upload()
|
|
{
|
|
|
|
// Check that the path is a directory
|
|
if (is_file($this->path)) {
|
|
$this->status = "error";
|
|
$this->message = "Path Not A Directory";
|
|
} else {
|
|
// Handle upload
|
|
$info = array();
|
|
foreach( $_FILES['upload']['name'] as $key=>$value) {
|
|
if (!empty($value)) {
|
|
$filename = $value;
|
|
$add = $this->path."/$filename";
|
|
if (@move_uploaded_file($_FILES['upload']['tmp_name'][$key], $add)) {
|
|
$info[] = array(
|
|
"name"=>$value,
|
|
"size"=>filesize($add),
|
|
"url"=>$add,
|
|
"thumbnail_url"=>$add,
|
|
"delete_url"=>$add,
|
|
"delete_type"=>"DELETE"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
$this->upload_json = json_encode($info);
|
|
}
|
|
|
|
$this->respond();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// RESPOND (Outputs data in JSON [JSEND] format)
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public function respond()
|
|
{
|
|
|
|
// Success ///////////////////////////////////////////////
|
|
if ($this->status=="success") {
|
|
if ($this->data) {
|
|
$json = '{"status":"success","data":{'.$this->data.'}}';
|
|
} else {
|
|
$json = '{"status":"success","data":null}';
|
|
}
|
|
|
|
// Upload JSON ///////////////////////////////////////////
|
|
} elseif ($this->upload_json!='') {
|
|
$json = $this->upload_json;
|
|
|
|
// Error /////////////////////////////////////////////////
|
|
} else {
|
|
$json = '{"status":"error","message":"'.$this->message.'"}';
|
|
}
|
|
|
|
// Output ////////////////////////////////////////////////
|
|
echo($json);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Clean a path
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
public static function cleanPath($path)
|
|
{
|
|
|
|
// replace backslash with slash
|
|
$path = str_replace('\\', '/', $path);
|
|
|
|
// allow only valid chars in paths$
|
|
$path = preg_replace('/[^A-Za-z0-9\-\._\/\ ]/', '', $path);
|
|
// maybe this is not needed anymore
|
|
// prevent Poison Null Byte injections
|
|
$path = str_replace(chr(0), '', $path);
|
|
|
|
// prevent go out of the workspace
|
|
while (strpos($path, '../') !== false) {
|
|
$path = str_replace('../', '', $path);
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
} |