forUser = $forUser;
parent::__construct($message, $code, $previous);
}
}
class IFM {
private $defaultconfig = [
// general config
"auth" => 0,
"auth_source" => 'inline;admin:$2y$10$0Bnm5L4wKFHRxJgNq.oZv.v7yXhkJZQvinJYR2p6X1zPvzyDRUVRC',
"auth_ignore_basic" => 0,
"root_dir" => "",
"root_public_url" => "",
"tmp_dir" => "",
"timezone" => "",
"forbiddenChars" => [],
"language" => "en",
"selfoverwrite" => 0,
"session_name" => false,
// api controls
"ajaxrequest" => 1,
"chmod" => 1,
"copymove" => 1,
"createdir" => 1,
"createfile" => 1,
"edit" => 1,
"delete" => 1,
"download" => 1,
"extract" => 1,
"upload" => 1,
"remoteupload" => 1,
"remoteupload_disable_ssrf_check" => 0,
"rename" => 1,
"zipnload" => 1,
"createarchive" => 1,
"search" => 1,
"paging" => 0,
"pageLength" => 50,
// gui controls
"showlastmodified" => 0,
"showfilesize" => 1,
"showowner" => 1,
"showgroup" => 1,
"showpermissions" => 2,
"showhtdocs" => 0,
"showhiddenfiles" => 1,
"showpath" => 0,
"contextmenu" => 1,
"disable_mime_detection" => 0,
"showrefresh" => 1,
"forceproxy" => 0,
"confirmoverwrite" => 1
];
private $config = [];
private $templates = [];
private $i18n = [];
public $mode = "standalone";
public function __construct($config=[]) {
// store initial working directory
$this->initialWD = getcwd();
// load the default config
$this->config = $this->defaultconfig;
// load config from environment variables
foreach (array_keys($this->config) as $key) {
if (($value = getenv('IFM_' . strtoupper($key))) !== false) {
if (is_numeric($value))
$value = intval($value);
$this->config[$key] = $value;
}
}
// load config from passed array
$this->config = array_merge($this->config, $config);
$i18n = [];
$i18n["en"] = <<<'f00bar'
{
"ajax_request": "AJAX request",
"archive_create_error": "Could not create archive.",
"archive_create_success": "Archive created.",
"archive_invalid_format": "Invalid archive format given.",
"archivename": "Name of the archive",
"cancel": "Cancel",
"close": "Close",
"copy": "Copy",
"copy_error": "The following files could not be copied:",
"copy_success": "Files copied.",
"copylink": "Copy link",
"create": "Create",
"create_archive": "Create archive",
"create_wo_close": "Create w/o close",
"data": "Data",
"delete": "Delete",
"directoryname": "Directory Name",
"download": "Download",
"edit": "Edit",
"editor_options": "Editor Options",
"error": "Error:",
"extract": "Extract",
"extract_error": "Could not extract archive.",
"extract_filename": "Extract file:",
"extract_success": "Archive extracted.",
"file_copy_to": "to",
"file_delete_confirm": "Do you really want to delete the following file:",
"file_delete_error": "Could not delete files.",
"file_delete_success": "Files deleted.",
"file_display_error": "This file cannot be displayed or edited.",
"file_load_error": "Could not load content.",
"file_new": "New file",
"file_no_permission": "No permission to edit/create file.",
"file_not_found": "Could either not find or open file.",
"file_open_error": "Could not open the file.",
"file_rename": "Rename File",
"file_rename_error": "File could not be renamed",
"file_rename_success": "File renamed.",
"file_save_confirm": "Do you want to save the following file:",
"file_save_error": "Could not save file.",
"file_save_success": "File saved.",
"file_upload_error": "Could not upload file.",
"file_upload_success": "File uploaded.",
"filename": "Filename",
"filename_new": "New Filename",
"filename_slashes": "Remove all slashes from the filename.",
"filter": "Filter",
"folder_create_error": "Could not create directory:",
"folder_create_success": "Directory created.",
"folder_new": "New Folder",
"folder_not_found": "Could not find the directory.",
"folder_tree_load_error": "Could not fetch folder tree.",
"footer": "IFM - improved file manager | ifm.php hidden |",
"general_error": "General error: No or broken response.",
"github": "Visit the project on GitHub",
"group": "Group",
"invalid_action": "Invalid action given.",
"invalid_archive_format": "Invalid archive format given. Possible formats are ZIP, TAR, tar.gz or tar.bz2.",
"invalid_data": "Invalid data from server.",
"invalid_dir": "Invalid directory given.",
"invalid_filename": "Invalid filename given.",
"invalid_params": "Invalid parameter given.",
"invalid_url": "Invalid URL given.",
"json_encode_error": "Could not format the response as JSON:",
"last_modified": "Last Modified",
"load_config_error": "Could not load configuration.",
"load_template_error": "Could not load templates.",
"load_text_error": "Could not load texts.",
"login": "Login",
"login_failed": "Login failed.",
"logout": "Log Off",
"method": "Method",
"move": "Move",
"move_error": "The following files could not be moved:",
"move_success": "Files moved.",
"nopermissions": "You lack the permissions to do that.",
"options": "Options",
"owner": "Owner",
"password": "Password",
"path_content": "Current directory",
"pattern_error_slashes": "Pattern cannot contain slashes.",
"permission_change_error": "Could not change permissions:",
"permission_change_success": "Permissions changed.",
"permission_parse_error": "Could not parse permissions.",
"permissions": "Permissions",
"refresh": "Refresh",
"remaining_tasks": "There are remaining tasks. Do you really want to reload?",
"rename": "Rename",
"rename_filename": "Rename file:",
"request": "Request",
"response": "Response",
"save": "Save",
"save_wo_close": "Save without Closing",
"search": "Search",
"search_pattern": "Pattern",
"select_destination": "Select Destination",
"size": "Size",
"soft_tabs": "Soft Tabs",
"tab_size": "Tab Size",
"tasks": "Tasks",
"toggle_nav": "Toggle navigation",
"upload": "Upload",
"upload_drop": "Drop files to upload",
"upload_file": "Upload File",
"upload_overwrite_confirm": "Upload anyway?",
"upload_overwrite_hint": "The following files will be overwritten:",
"upload_remote": "Remote Upload",
"upload_remote_url": "Remote Upload URL",
"url_not_allowed": "This URL is not allowed.",
"username": "Username",
"word_wrap": "Word Wrap"
}
f00bar;
$i18n["en"] = json_decode( $i18n["en"], true );
$this->i18n = $i18n;
if ($this->config['timezone'])
date_default_timezone_set($this->config['timezone']);
if ($this->config['session_name'])
session_name($this->config['session_name']);
// set cookie_path for SESSION to REQUEST_URI without QUERY_STRING
$cookie_path = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '?') ?: strlen($_SERVER['REQUEST_URI']));
session_set_cookie_params(0, $cookie_path);
}
/**
* This function contains the client-side application
*/
public function getApplication() {
$this->getHTMLHeader();
print '
';
$this->getJS();
print '';
$this->getHTMLFooter();
}
public function getInlineApplication() {
$this->getCSS();
print '';
$this->getJS();
}
public function getCSS() {
echo <<<'f00bar'
f00bar;
}
public function getJS() {
echo <<<'f00bar'
f00bar;
}
public function getHTMLHeader() {
print '
IFM - improved file manager
';
$this->getCSS();
print '';
}
public function getHTMLFooter() {
print '';
}
/**
* main functions
*/
public function run($mode="standalone") {
try {
if (!is_dir(realpath($this->config['root_dir'])) || !is_readable(realpath($this->config['root_dir'])))
throw new IFMException("Cannot access root_dir.", false);
chdir(realpath($this->config['root_dir']));
$this->mode = $mode;
if (isset($_REQUEST['api']) || $mode == "api")
$this->jsonResponse($this->dispatch());
elseif ($mode == "standalone")
$this->getApplication();
else
$this->getInlineApplication();
} catch (IFMException $e) {
$this->jsonResponse(["status" => "ERROR", "message" => $e->getMessage()]);
} catch (Exception $e) {
$this->jsonResponse(["status" => "ERROR", "message" => $e->getMessage()]);
}
}
private function dispatch() {
// APIs which do not need authentication
switch ($_REQUEST['api']) {
case "checkAuth":
if ($this->checkAuth())
return ["status" => "OK", "message" => "Authenticated"];
else
return ["status" => "ERROR", "message" => "Not authenticated"];
case "getConfig":
return $this->getConfig();
case "getTemplates":
return $this->getTemplates();
case "getI18N":
return $this->getI18N($_REQUEST);
case "logout":
session_start();
session_unset();
header("Location: " . strtok($_SERVER["REQUEST_URI"], '?'));
exit;
}
// check authentication
if (!$this->checkAuth())
throw new IFMException("Not authenticated");
// api requests which work without a valid working directory
switch ($_REQUEST['api']) {
case "getRealpath":
if (isset($_REQUEST["dir"]) && $_REQUEST["dir"] != "")
return ["realpath" => $this->getValidDir($_REQUEST["dir"])];
else
return ["realpath" => ""];
case "getFiles":
if (isset($_REQUEST["dir"]) && $this->isPathValid($_REQUEST["dir"]))
return $this->getFiles($_REQUEST["dir"]);
else
return $this->getFiles("");
case "getFolders":
return $this->getFolders($_REQUEST);
}
// checking working directory
if (!isset($_REQUEST["dir"]) || !$this->isPathValid($_REQUEST["dir"]))
throw new IFMException($this->l("invalid_dir"));
$this->chDirIfNecessary($_REQUEST['dir']);
switch ($_REQUEST["api"]) {
case "createDir": return $this->createDir($_REQUEST);
case "saveFile": return $this->saveFile($_REQUEST);
case "getContent": return $this->getContent($_REQUEST);
case "delete": return $this->deleteFiles($_REQUEST);
case "rename": return $this->renameFile($_REQUEST);
case "download": return $this->downloadFile($_REQUEST);
case "extract": return $this->extractFile($_REQUEST);
case "upload": return $this->uploadFile($_REQUEST);
case "copyMove": return $this->copyMove($_REQUEST);
case "changePermissions": return $this->changePermissions($_REQUEST);
case "zipnload": return $this->zipnload($_REQUEST);
case "remoteUpload": return $this->remoteUpload($_REQUEST);
case "searchItems": return $this->searchItems($_REQUEST);
case "getFolderTree": return $this->getFolderTree($_REQUEST);
case "createArchive": return $this->createArchive($_REQUEST);
case "proxy": return $this->downloadFile($_REQUEST, false);
default:
throw new IFMException($this->l("invalid_action"));
}
}
/**
* api functions
*/
private function getI18N($lang="en") {
if (in_array($lang, array_keys($this->i18n)))
return array_merge($this->i18n['en'], $this->i18n[$lang]);
else
return $this->i18n['en'];
}
private function getTemplates() {
// templates
$templates = [];
$templates['app'] = <<<'f00bar'
{{i18n.upload_drop}}
|
{{i18n.filename}} |
{{#config.download}}
|
{{/config.download}}
{{#config.showlastmodified}}
{{i18n.last_modified}} |
{{/config.showlastmodified}}
{{#config.showfilesize}}
{{i18n.size}} |
{{/config.showfilesize}}
{{#config.showpermissions}}
{{i18n.permissions}} |
{{/config.showpermissions}}
{{#config.showowner}}
{{i18n.owner}} |
{{/config.showowner}}
{{#config.showgroup}}
{{i18n.group}} |
{{/config.showgroup}}
|
f00bar;
$templates['login'] = <<<'f00bar'
f00bar;
$templates['filetable'] = <<<'f00bar'
{{#items}}
{{#fixtop}}
|
{{/fixtop}}
{{^fixtop}}
|
{{/fixtop}}
{{linkname}}
|
{{#config.download}}
|
{{/config.download}}
{{#config.showlastmodified}}
{{lastmodified_hr}} |
{{/config.showlastmodified}}
{{#config.showfilesize}}
{{size}} |
{{/config.showfilesize}}
{{#config.showpermissions}}
|
{{/config.showpermissions}}
{{#config.showowner}}
{{owner}}
|
{{/config.showowner}}
{{#config.showgroup}}
{{group}}
|
{{/config.showgroup}}
{{#button}}
{{/button}}
|
{{/items}}
f00bar;
$templates['footer'] = <<<'f00bar'
f00bar;
$templates['task'] = <<<'f00bar'
f00bar;
$templates['ajaxrequest'] = <<<'f00bar'
f00bar;
$templates['copymove'] = <<<'f00bar'
f00bar;
$templates['createdir'] = <<<'f00bar'
f00bar;
$templates['createarchive'] = <<<'f00bar'
f00bar;
$templates['deletefile'] = <<<'f00bar'
f00bar;
$templates['extractfile'] = <<<'f00bar'
f00bar;
$templates['file'] = <<<'f00bar'
f00bar;
$templates['file_editoroptions'] = <<<'f00bar'
f00bar;
$templates['remoteupload'] = <<<'f00bar'
f00bar;
$templates['renamefile'] = <<<'f00bar'
f00bar;
$templates['search'] = <<<'f00bar'
f00bar;
$templates['searchresults'] = <<<'f00bar'
{{#items}}
{{linkname}} ({{folder}})
|
{{/items}}
{{^items}}
No results found.
|
{{/items}}
f00bar;
$templates['uploadfile'] = <<<'f00bar'
f00bar;
$templates['uploadconfirmoverwrite'] = <<<'f00bar'
f00bar;
return $templates;
}
private function getFiles($dir) {
$this->chDirIfNecessary($dir);
unset($files); unset($dirs); $files = []; $dirs = [];
if ($handle = opendir(".")) {
while (false !== ($result = readdir($handle))) {
if ($result == basename($_SERVER['SCRIPT_NAME']) && getcwd() == $this->initialWD)
continue;
elseif (($result == ".htaccess" || $result==".htpasswd") && $this->config['showhtdocs'] != 1)
continue;
elseif ($result == ".")
continue;
elseif ($result != ".." && substr($result, 0, 1) == "." && $this->config['showhiddenfiles'] != 1)
continue;
else {
$item = $this->getItemInformation($result);
if ($item['type'] == "dir")
$dirs[] = $item;
else
$files[] = $item;
}
}
closedir($handle);
}
array_multisort(array_column($dirs, 'name'), SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $dirs);
array_multisort(array_column($files, 'name'), SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $files);
return array_merge($dirs, $files);
}
private function getItemInformation($name) {
$item = [];
$item["name"] = $name;
if (is_dir($name)) {
$item["type"] = "dir";
if ($name == "..")
$item["icon"] = "icon icon-up-open";
else
$item["icon"] = "icon icon-folder-empty";
} else {
$item["type"] = "file";
if (in_array(substr($name, -7), [".tar.gz", ".tar.xz"]))
$type = substr($name, -6);
elseif (substr($name, -8) == ".tar.bz2")
$type = "tar.bz2";
else
$type = substr(strrchr($name, "."), 1);
$item["icon"] = $this->getTypeIcon($type);
$item["ext"] = strtolower($type);
if (!$this->config['disable_mime_detection'])
$item["mime_type"] = mime_content_type($name);
}
if ($this->config['showlastmodified'] == 1)
$item["lastmodified"] = filemtime($name);
if ($this->config['showfilesize'] == 1) {
if ($item['type'] == "dir") {
$item['size_raw'] = 0;
$item['size'] = "";
} else {
$item["size_raw"] = filesize($name);
if ($item["size_raw"] > 1073741824) $item["size"] = round(($item["size_raw"]/1073741824 ), 2) . " GB";
elseif($item["size_raw"]>1048576)$item["size"] = round(($item["size_raw"]/1048576), 2) . " MB";
elseif($item["size_raw"]>1024)$item["size"] = round(($item["size_raw"]/1024), 2) . " KB";
else $item["size"] = $item["size_raw"] . " Byte";
}
}
if ($this->config['showpermissions'] > 0) {
if ($this->config['showpermissions'] == 1)
$item["fileperms"] = substr(decoct(fileperms($name)), -3);
elseif ($this->config['showpermissions'] == 2)
$item["fileperms"] = $this->filePermsDecode(fileperms($name));
if ($item["fileperms"] == "")
$item["fileperms"] = " ";
$item["filepermmode"] = ($this->config['showpermissions'] == 1) ? "short" : "long";
}
if ($this->config['showowner'] == 1) {
if (function_exists("posix_getpwuid") && fileowner($name) !== false) {
$ownerarr = posix_getpwuid(fileowner($name));
$item["owner"] = $ownerarr['name'];
} else $item["owner"] = false;
}
if ($this->config['showgroup'] == 1) {
if (function_exists("posix_getgrgid") && filegroup($name) !== false) {
$grouparr = posix_getgrgid(filegroup($name));
$item["group"] = $grouparr['name'];
} else $item["group"] = false;
}
return $item;
}
private function getConfig() {
$ret = $this->config;
$ret['inline'] = ($this->mode == "inline") ? true : false;
$ret['isDocroot'] = ($this->getRootDir() == $this->initialWD);
foreach (["auth_source", "root_dir"] as $field)
unset($ret[$field]);
return $ret;
}
private function getFolders($d) {
if (!isset($d['dir']))
$d['dir'] = $this->getRootDir();
if (!$this->isPathValid($d['dir']))
return [];
else {
$ret = [];
foreach (glob($this->pathCombine($d['dir'], "*"), GLOB_ONLYDIR) as $dir) {
array_push($ret, [
"text" => htmlspecialchars(basename($dir)),
"lazyLoad" => true,
"dataAttr" => ["path" => $dir]
]);
}
sort($ret);
if (realpath($d['dir']) == $this->initialWD)
$ret = array_merge(
[
0 => [
"text" => "/ [root]",
"dataAttr" => ["path" => $this->getRootDir()]
]
],
$ret
);
return $ret;
}
}
private function searchItems($d) {
if ($this->config['search'] != 1)
throw new IFMException($this->l('nopermissions'));
if (strpos($d['pattern'], '/') !== false)
throw new IFMException($this->l('pattern_error_slashes'));
$results = $this->searchItemsRecursive($d['pattern']);
return $results;
}
private function searchItemsRecursive($pattern, $dir="") {
$items = [];
$dir = $dir ?? '.';
foreach (glob($this->pathCombine($dir, $pattern)) as $result)
array_push($items, $this->getItemInformation($result));
foreach (glob($this->pathCombine($dir, '*'), GLOB_ONLYDIR) as $subdir)
$items = array_merge($items, $this->searchItemsRecursive($pattern, $subdir));
return $items;
}
private function getFolderTree($d) {
return array_merge(
[
0 => [
"text" => "/ [root]",
"nodes" => [],
"dataAttributes" => ["path" => $this->getRootDir()]
]
],
$this->getFolderTreeRecursive($d['dir'])
);
}
private function getFolderTreeRecursive($start_dir) {
$ret = [];
$start_dir = realpath($start_dir);
if ($handle = opendir($start_dir)) {
while (false !== ($result = readdir($handle))) {
if (is_dir($this->pathCombine($start_dir, $result)) && $result != "." && $result != ".." ) {
array_push($ret, [
"text" => htmlspecialchars($result),
"dataAttributes" => ["path" => $this->pathCombine($start_dir, $result)],
"nodes" => $this->getFolderTreeRecursive($this->pathCombine($start_dir, $result))
]);
}
}
}
sort($ret);
return $ret;
}
private function copyMove($d) {
if ($this->config['copymove'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!isset($d['destination']) || !$this->isPathValid(realpath($d['destination'])))
throw new IFMException($this->l('invalid_dir'));
if (!is_array($d['filenames']))
throw new IFMException($this->l('invalid_params'));
if (!in_array($d['action'], ['copy', 'move']))
throw new IFMException($this->l('invalid_action'));
$err = []; $errFlag = -1; // -1 -> all errors; 0 -> at least some errors; 1 -> no errors
foreach ($d['filenames'] as $file) {
if (!file_exists($file) || $file == ".." || !$this->isFilenameValid($file)) {
array_push($err, $file);
}
if ($d['action'] == "copy") {
if ($this->xcopy($file, $d['destination']))
$errFlag = 0;
else
array_push($err, $file);
} elseif ($d['action'] == "move") {
if (rename($file, $this->pathCombine($d['destination'], basename($file))))
$errFlag = 0;
else
array_push($err, $file);
}
}
$action = ($d['action'] == "copy") ? "copied" : "moved";
if (empty($err)) {
return [
"status" => "OK",
"message" => ($d['action'] == "copy" ? $this->l('copy_success') : $this->l('move_success')),
"errflag" => "1"
];
} else {
$errmsg = ($d['action'] == "copy" ? $this->l('copy_error') : $this->l('move_error')) . "";
foreach ($err as $item)
$errmsg .= "- ".$item."
";
$errmsg .= "
";
throw new IFMException($errmsg);
}
}
// creates a directory
private function createDir($d) {
if ($this->config['createdir'] != 1)
throw new IFMException($this->l('nopermissions'));
if ($d['dirname'] == "" || !$this->isFilenameValid($d['dirname']))
throw new IFMException($this->l('invalid_dir'));
if (@mkdir($d['dirname']))
return ["status" => "OK", "message" => $this->l('folder_create_success')];
else
throw new IFMException($this->l('folder_create_error').". ".error_get_last()['message']);
}
// save a file
private function saveFile($d) {
if (
(file_exists($this->pathCombine($d['dir'], $d['filename'])) && $this->config['edit'] != 1 )
|| (!file_exists($this->pathCombine($d['dir'], $d['filename'])) && $this->config['createfile'] != 1)
)
throw new IFMException($this->l('nopermissions'));
if (isset($d['filename']) && $this->isFilenameValid($d['filename'])) {
if (isset($d['content'])) {
// work around magic quotes
if((function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc())
|| (ini_get('magic_quotes_sybase') && (strtolower(ini_get('magic_quotes_sybase'))!="off")) ) {
$content = stripslashes($d['content']);
} else {
$content = $d['content'];
}
if (@file_put_contents($d['filename'], $content) !== false)
return ["status" => "OK", "message" => $this->l('file_save_success')];
else
throw new Exception($this->l('file_save_error'));
} else
throw new IFMException($this->l('file_save_error'));
} else
throw new IFMException($this->l('invalid_filename'));
}
// gets the content of a file
// notice: if the content is not JSON encodable it returns an error
private function getContent($d) {
if ($this->config['edit'] != 1)
throw new IFMException($this->l('nopermissions'));
if (isset($d['filename']) && $this->isFilenameAllowed($d['filename']) && file_exists($d['filename']) && is_readable($d['filename'])) {
$content = @file_get_contents($d['filename']);
if (function_exists("mb_check_encoding") && !mb_check_encoding($content, "UTF-8"))
$content = utf8_encode($content);
return ["status" => "OK", "data" => ["filename" => $d['filename'], "content" => $content]];
} else
throw new IFMException($this->l('file_not_found'));
}
// deletes a bunch of files or directories
private function deleteFiles($d) {
if ($this->config['delete'] != 1)
throw new IFMException($this->l('nopermissions'));
$err = []; $errFLAG = -1; // -1 -> no files deleted; 0 -> at least some files deleted; 1 -> all files deleted
foreach ($d['filenames'] as $file) {
if ($this->isFilenameAllowed($file)) {
if (is_dir($file)) {
$res = $this->rec_rmdir($file);
if ($res != 0)
array_push($err, $file);
else
$errFLAG = 0;
} else {
if (@unlink($file))
$errFLAG = 0;
else
array_push($err, $file);
}
} else {
array_push($err, $file);
}
}
if (empty($err))
return ["status" => "OK", "message" => $this->l('file_delete_success'), "errflag" => "1"];
else {
$errmsg = $this->l('file_delete_error') . "";
foreach ($err as $item)
$errmsg .= "- ".$item."
";
$errmsg .= "
";
throw new IFMException($errmsg);
}
}
// renames a file
private function renameFile(array $d) {
if ($this->config['rename'] != 1)
throw new IFMException($this->l('nopermissions'));
elseif (!$this->isFilenameValid($d['filename']) || !$this->isFilenameValid($d['newname']))
throw new IFMException($this->l('invalid_filename'));
if (@rename($d['filename'], $d['newname']))
return ["status" => "OK", "message" => $this->l('file_rename_success')];
else
throw new IFMException($this->l('file_rename_error'));
}
// provides a file for downloading
private function downloadFile(array $d, $forceDL=true) {
if ($this->config['download'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!$this->isFilenameValid($d['filename']))
throw new IFMException($this->l('invalid_filename'));
if (!is_file($d['filename']))
http_response_code(404);
else
$this->fileDownload(["file" => $d['filename'], "forceDL" => $forceDL]);
}
// extracts a zip-archive
private function extractFile(array $d) {
$restoreIFM = false;
$tmpSelfContent = null;
$tmpSelfChecksum = null;
if ($this->config['extract'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!file_exists($d['filename']))
throw new IFMException($this->l('invalid_filename'));
if (!isset($d['targetdir']) || trim($d['targetdir']) == "")
$d['targetdir'] = "./";
if (!$this->isPathValid($d['targetdir']))
throw new IFMException($this->l('invalid_dir'));
if (!is_dir($d['targetdir']) && !mkdir($d['targetdir'], 0777, true))
throw new IFMException($this->l('folder_create_error'));
if (realpath($d['targetdir']) == substr($this->initialWD, 0, strlen(realpath($d['targetdir'])))) {
$tmpSelfContent = tmpfile();
fwrite($tmpSelfContent, file_get_contents(__FILE__));
$tmpSelfChecksum = hash_file("sha256", __FILE__);
$restoreIFM = true;
}
if (strtolower(pathinfo($d['filename'], PATHINFO_EXTENSION) == "zip")) {
if (!IFMArchive::extractZip($d['filename'], $d['targetdir']))
throw new IFMException($this->l('extract_error'));
else
return ["status" => "OK","message" => $this->l('extract_success')];
} elseif (
(strtolower(pathinfo($d['filename'], PATHINFO_EXTENSION)) == "tar")
|| (strtolower(pathinfo(pathinfo($d['filename'], PATHINFO_FILENAME), PATHINFO_EXTENSION)) == "tar")
) {
if (!IFMArchive::extractTar($d['filename'], $d['targetdir']))
throw new IFMException($this->l('extract_error'));
else
return ["status" => "OK","message" => $this->l('extract_success')];
} else {
throw new IFMException($this->l('archive_invalid_format'));
}
if ($restoreIFM) {
if ($tmpSelfChecksum != hash_file("sha256", __FILE__)) {
rewind($tmpSelfContent);
$fh = fopen(__FILE__, "w");
while (!feof($tmpSelfContent))
fwrite($fh, fread($tmpSelfContent, 8196));
fclose($fh);
}
fclose($tmpSelfContent);
}
}
// uploads a file
private function uploadFile(array $d) {
if($this->config['upload'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!isset($_FILES['file']))
throw new IFMException($this->l('file_upload_error'));
$newfilename = (isset($d["newfilename"]) && $d["newfilename"]!="") ? $d["newfilename"] : $_FILES['file']['name'];
if (!$this->isFilenameValid($newfilename))
throw new IFMException($this->l('invalid_filename'));
if ($_FILES['file']['tmp_name']) {
if (is_writable(getcwd())) {
if (move_uploaded_file($_FILES['file']['tmp_name'], $newfilename))
return ["status" => "OK", "message" => $this->l('file_upload_success'), "cd" => $d['dir']];
else
throw new IFMException($this->l('file_upload_error'));
} else
throw new IFMException($this->l('file_upload_error'));
} else
throw new IFMException($this->l('file_not_found'));
}
// change permissions of a file
private function changePermissions(array $d) {
if ($this->config['chmod'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!isset($d["chmod"]) || $d['chmod'] == "" )
throw new IFMException($this->l('permission_parse_error'));
if (!$this->isPathValid($this->pathCombine($d['dir'], $d['filename'])))
throw new IFMException($this->l('nopermissions'));
$chmod = $d["chmod"]; $cmi = true;
if (!is_numeric($chmod)) {
$cmi = false;
$chmod = str_replace(" ", "", $chmod);
if (strlen($chmod) == 9) {
$cmi = true;
$arr = [substr($chmod, 0, 3), substr($chmod, 3, 3), substr($chmod, 6, 3)];
$chtmp = "0";
foreach ($arr as $right) {
$rtmp = 0;
if (substr($right, 0, 1) == "r") $rtmp = $rtmp + 4; elseif (substr($right, 0, 1) <> "-") $cmi = false;
if (substr($right, 1, 1) == "w") $rtmp = $rtmp + 2; elseif (substr($right, 1, 1) <> "-") $cmi = false;
if (substr($right, 2, 1) == "x") $rtmp = $rtmp + 1; elseif (substr($right, 2, 1) <> "-") $cmi = false;
$chtmp = $chtmp . $rtmp;
}
$chmod = intval($chtmp);
}
} else
$chmod = "0" . $chmod;
if ($cmi) {
try {
chmod($d["filename"], (int)octdec($chmod));
return ["status" => "OK", "message" => $this->l('permission_change_success')];
} catch (Exception $e) {
throw new IFMException($this->l('permission_change_error'));
}
} else
throw new IFMException($this->l('permission_parse_error'));
}
// zips a directory and provides it for downloading
// it creates a temporary zip file in the current directory, so it has to be as much space free as the file size is
private function zipnload(array $d) {
if ($this->config['zipnload'] != 1)
throw new IFMException($this->l('nopermission'));
if (!file_exists($d['filename']))
throw new IFMException($this->l('folder_not_found'));
if (!$this->isPathValid($d['filename']))
throw new IFMException($this->l('invalid_dir'));
if ($d['filename'] != "." && !$this->isFilenameValid($d['filename']))
throw new IFMException($this->l('invalid_filename'));
unset($zip);
if ($this->isAbsolutePath($this->config['tmp_dir']))
$dfile = $this->pathCombine($this->config['tmp_dir'], uniqid("ifm-tmp-") . ".zip"); // temporary filename
else
$dfile = $this->pathCombine($this->initialWD, $this->config['tmp_dir'], uniqid("ifm-tmp-") . ".zip"); // temporary filename
try {
IFMArchive::createZip(realpath($d['filename']), $dfile, [$this, 'isFilenameValid']);
if ($d['filename'] == ".") {
if (getcwd() == $this->getRootDir())
$d['filename'] = "root";
else
$d['filename'] = basename(getcwd());
}
$this->fileDownload(["file" => $dfile, "name" => $d['filename'] . ".zip", "forceDL" => true]);
} catch (Exception $e) {
throw new IFMException($this->l('error') . " " . $e->getMessage());
} finally {
if (file_exists($dfile))
@unlink($dfile);
}
}
// uploads a file from an other server using the curl extention
private function remoteUpload(array $d) {
if ($this->config['remoteupload'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!isset($d['method']) || !in_array($d['method'], ["curl", "file"]))
throw new IFMException($this->l('invalid_params'));
if ($this->config['remoteupload_disable_ssrf_check'] != 1)
if (!$this->checkUrlSsrf($d['url']))
throw new IFMException($this->l('url_not_allowed'));
if ($d['method'] == "curl" && $this->checkCurl() == false)
throw new IFMException($this->l('error')." cURL extention not installed.");
if ($d['method'] == "curl") {
$filename = (isset($d['filename']) && $d['filename'] != "") ? $d['filename'] : "curl_".uniqid();
$ch = curl_init();
if ($ch) {
if ($this->isFilenameValid($filename) == false)
throw new IFMException($this->l('invalid_filename'));
else {
$fp = fopen($filename, "w");
if ($fp) {
if (
!curl_setopt($ch, CURLOPT_URL, urldecode($d['url']))
|| !curl_setopt($ch, CURLOPT_FILE, $fp)
|| !curl_setopt($ch, CURLOPT_HEADER, 0)
|| !curl_exec($ch)
)
throw new IFMException($this->l('error')." ".curl_error($ch));
else
return ["status" => "OK", "message" => $this->l('file_upload_success')];
curl_close($ch);
fclose($fp);
} else
throw new IFMException($this->l('file_open_error'));
}
} else
throw new IFMException($this->l('error')." curl init");
} elseif ($d['method'] == 'file') {
$filename = (isset($d['filename']) && $d['filename'] != "") ? $d['filename'] : "curl_".uniqid();
if ($this->isFilenameValid($filename) == false)
throw new IFMException($this->l('invalid_filename'));
else {
try {
file_put_contents($filename, file_get_contents($d['url']));
return ["status" => "OK", "message" => $this->l('file_upload_success')];
} catch (Exception $e) {
throw new IFMException($this->l('error') . " " . $e->getMessage());
}
}
} else
throw new IFMException($this->l('invalid_params'));
}
private function createArchive($d) {
if ($this->config['createarchive'] != 1)
throw new IFMException($this->l('nopermissions'));
if (!$this->isFilenameValid($d['archivename']))
throw new IFMException($this->l('invalid_filename'));
$filenames = [];
foreach ($d['filenames'] as $file)
if (!$this->isFilenameValid($file))
throw new IFMException($this->l('invalid_filename'));
else
array_push($filenames, realpath($file));
switch ($d['format']) {
case "zip":
if (IFMArchive::createZip($filenames, $d['archivename']))
return ["status" => "OK", "message" => $this->l('archive_create_success')];
else
throw new IFMException($this->l('archive_create_error'));
break;
case "tar":
$d['archivename'] = pathinfo($d['archivename'], PATHINFO_FILENAME);
if (IFMArchive::createTar($filenames, $d['archivename'], $d['format']))
return ["status" => "OK", "message" => $this->l('archive_create_success')];
else
throw new IFMException($this->l('archive_create_error'));
break;
case "tar.gz":
case "tar.bz2":
$d['archivename'] = pathinfo(pathinfo($d['archivename'], PATHINFO_FILENAME), PATHINFO_FILENAME);
if (IFMArchive::createTar($filenames, $d['archivename'], $d['format']))
return ["status" => "OK", "message" => $this->l('archive_create_success')];
else
throw new IFMException($this->l('archive_create_error'));
break;
default:
throw new IFMException($this->l('archive_invalid_format'));
break;
}
}
/**
* help functions
*/
private function l($str) {
if (isset($_REQUEST['lang'])
&& in_array($_REQUEST['lang'], array_keys($this->i18n))
&& isset($this->i18n[$_REQUEST['lang']][$str]))
return $this->i18n[$_REQUEST['lang']][$str];
else
return $this->i18n['en'][$str];
}
private function log($d) {
file_put_contents($this->pathCombine($this->getRootDir(), "debug.ifm.log"), (is_array($d) ? print_r($d, true)."\n" : $d."\n"), FILE_APPEND);
}
private function jsonResponse($array) {
$this->convertToUTF8($array);
$json = json_encode($array);
if ($json === false) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
echo ' - No errors';
break;
case JSON_ERROR_DEPTH:
echo ' - Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
echo ' - Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
echo ' - Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
echo ' - Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
echo ' - Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
echo ' - Unknown error';
break;
}
throw new IFMException($this->l('json_encode_error')." ".$err);
} else
echo $json;
}
private function convertToUTF8(&$item) {
if (is_array($item))
array_walk($item, [$this, 'convertToUTF8']);
else
if (function_exists("mb_check_encoding") && !mb_check_encoding($item, "UTF-8"))
$item = utf8_encode($item);
}
private function checkAuth() {
if ($this->config['auth'] == 0)
return true;
$credentials_header = $_SERVER['HTTP_X_IFM_AUTH'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? false;
if ($credentials_header && !$this->config['auth_ignore_basic']) {
$cred = explode(":", base64_decode(str_replace("Basic ", "", $credentials_header)), 2);
if (count($cred) == 2 && $this->checkCredentials($cred[0], $cred[1]))
return true;
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (isset($_SESSION['ifmauth']) && $_SESSION['ifmauth'] == true)
return true;
$login_failed = false;
if (isset($_POST["inputLogin"]) && isset($_POST["inputPassword"])) {
if ($this->checkCredentials($_POST["inputLogin"], $_POST["inputPassword"])) {
$_SESSION['ifmauth'] = true;
} else {
$_SESSION['ifmauth'] = false;
$login_failed = true;
}
}
if (isset($_SESSION['ifmauth']) && $_SESSION['ifmauth'] === true)
return true;
else {
if ($login_failed === true)
throw new IFMException("Authentication failed: Wrong credentials", true);
else
throw new IFMException("Not authenticated" , true);
}
}
private function checkCredentials($user, $pass) {
list($src, $srcopt) = explode(";", $this->config['auth_source'], 2);
switch ($src) {
case "inline":
list($uname, $hash) = explode(":", $srcopt);
$htpasswd = new Htpasswd();
return $htpasswd->verifyPassword($pass, $hash) ? ($uname == $user) : false;
break;
case "file":
if (@file_exists($srcopt) && @is_readable($srcopt)) {
$htpasswd = new Htpasswd($srcopt);
return $htpasswd->verify($user, $pass);
} else {
trigger_error("IFM: Fatal: Credential file does not exist or is not readable");
return false;
}
break;
case "ldap":
$authenticated = false;
$ldapopts = explode(";", $srcopt);
if (count($ldapopts) === 4) {
list($ldap_server, $basedn, $uuid, $ufilter) = explode(";", $srcopt);
} else {
list($ldap_server, $basedn) = explode(";", $srcopt);
$ufilter = false;
$uuid = "uid";
}
$u = $uuid . "=" . $user . "," . $basedn;
if (!$ds = ldap_connect($ldap_server)) {
throw new IFMException("Could not reach the ldap server." , true);
//trigger_error("Could not reach the ldap server.", E_USER_ERROR);
return false;
}
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
if ($ds) {
$ldbind = @ldap_bind($ds, $u, $pass);
if ($ldbind) {
if ($ufilter) {
if (ldap_count_entries($ds, ldap_search($ds, $u, $ufilter)) == 1) {
$authenticated = true;
} else {
throw new IFMException("User not allowed." , true);
//trigger_error("User not allowed.", E_USER_ERROR);
$authenticated = false;
}
} else
$authenticated = true;
} else {
throw new IFMException(ldap_error($ds) , true);
//trigger_error(ldap_error($ds), E_USER_ERROR);
$authenticated = false;
}
ldap_unbind($ds);
} else
$authenticated = false;
return $authenticated;
break;
}
return false;
}
private function filePermsDecode($perms) {
$oct = str_split(strrev(decoct($perms)), 1);
$masks = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'];
return(
sprintf(
'%s %s %s',
array_key_exists($oct[2], $masks) ? $masks[$oct[2]] : '###',
array_key_exists($oct[1], $masks) ? $masks[$oct[1]] : '###',
array_key_exists($oct[0], $masks) ? $masks[$oct[0]] : '###')
);
}
private function isAbsolutePath($path) {
if ($path === null || $path === '')
return false;
return $path[0] === DIRECTORY_SEPARATOR || preg_match('~^[A-Z]:(?![^/\\\\])~i',$path) > 0;
}
private function getRootDir() {
if ($this->config['root_dir'] == "")
return $this->initialWD;
elseif ($this->isAbsolutePath($this->config['root_dir']))
return realpath($this->config['root_dir']);
else
return realpath($this->pathCombine($this->initialWD, $this->config['root_dir']));
}
private function getValidDir($dir) {
if (!$this->isPathValid($dir) || !is_dir($dir))
return "";
else {
$rpDir = realpath($dir);
$rpConfig = $this->getRootDir();
if ($rpConfig == "/")
return $rpDir;
elseif ($rpDir == $rpConfig)
return "";
else {
$part = substr($rpDir, strlen($rpConfig));
$part = (in_array(substr($part, 0, 1), ["/", "\\"])) ? substr($part, 1) : $part;
return $part;
}
}
}
private function isPathValid($dir) {
/**
* This function is also used to check non-existent paths, but the PHP realpath function returns false for
* nonexistent paths. Hence we need to check the path manually in the following lines.
*/
$tmp_d = $dir;
$tmp_missing_parts = [];
while (realpath($tmp_d) === false) {
$tmp_i = pathinfo($tmp_d, PATHINFO_FILENAME);
array_push($tmp_missing_parts, $tmp_i);
$tmp_d = dirname($tmp_d);
if ($tmp_d == dirname($tmp_d))
break;
}
$rpDir = $this->pathCombine(realpath($tmp_d), implode("/", array_reverse($tmp_missing_parts)));
$rpConfig = $this->getRootDir();
if (!is_string($rpDir) || !is_string($rpConfig)) // can happen if open_basedir is in effect
return false;
elseif ($rpDir == $rpConfig)
return true;
elseif (!file_exists($rpDir))
return false;
elseif (0 === strpos($rpDir, $rpConfig))
return true;
else
return false;
}
private function chDirIfNecessary($d) {
if (substr(getcwd(), strlen($this->getRootDir())) != $this->getValidDir($d) && !empty($d))
chdir($d);
}
private function getTypeIcon($type) {
$type = strtolower($type);
switch ($type) {
case "aac": case "aiff": case "mid": case "mp3": case "wav": return 'icon icon-file-audio'; break;
case "ai": case "bmp": case "eps": case "tiff": case "gif": case "jpg": case "jpeg": case "png": case "psd": case "svg": case "webp": return 'icon icon-file-image'; break;
case "avi": case "flv": case "mp4": case "mpg": case "mkv": case "mpeg": case "webm": case "wmv": case "mov": return 'icon icon-file-video'; break;
case "c": case "cpp": case "css": case "dat": case "h": case "html": case "java": case "js": case "php": case "py": case "sql": case "xml": case "yml": case "json": return 'icon icon-file-code'; break;
case "doc": case "docx": case "odf": case "odt": case "rtf": return 'icon icon-file-word'; break;
case "txt": case "log": return 'icon icon-doc-text'; break;
case "ods": case "xls": case "xlsx": return 'icon icon-file-excel'; break;
case "odp": case "ppt": case "pptx": return 'icon icon-file-powerpoint'; break;
case "pdf": return 'icon icon-file-pdf'; break;
case "tgz": case "zip": case "tar": case "tgz": case "tar.gz": case "tar.xz": case "tar.bz2": case "7z": case "rar": return 'icon icon-file-archive';
default: return 'icon icon-doc';
}
}
private function rec_rmdir($path) {
if (!is_dir($path))
return -1;
$dir = @opendir($path);
if (!$dir)
return -2;
while (($entry = @readdir($dir)) !== false) {
if ($entry == '.' || $entry == '..') continue;
if (is_dir($path . '/' . $entry)) {
$res = $this->rec_rmdir($path.'/'.$entry);
if ($res == -1) {
@closedir($dir);
return -2;
} else if ($res == -2) {
@closedir($dir);
return -2;
} else if ($res == -3) {
@closedir($dir);
return -3;
} else if ($res != 0) {
@closedir($dir);
return -2;
}
} else if (is_file($path.'/'.$entry) || is_link($path.'/'.$entry)) {
$res = @unlink($path.'/'.$entry);
if (!$res) {
@closedir($dir);
return -2;
}
} else {
@closedir($dir);
return -3;
}
}
@closedir($dir);
$res = @rmdir($path);
if (!$res)
return -2;
return 0;
}
private function xcopy($source, $dest) {
$isDir = is_dir($source);
if ($isDir)
$dest = $this->pathCombine($dest, basename($source));
if (!is_dir($dest))
mkdir($dest, 0777, true);
if (is_file($source))
return copy($source, $this->pathCombine($dest, basename($source)));
chdir($source);
foreach (glob('*') as $item)
$this->xcopy($item, $dest);
chdir('..');
return true;
}
// combines two parts to a valid path
private function pathCombine(...$parts) {
$ret = "";
foreach ($parts as $part)
if (trim($part) != "")
$ret .= (empty($ret) ? rtrim($part,"/") : trim($part, '/'))."/";
return rtrim($ret, "/");
}
// check if filename is allowed
public function isFilenameValid($f) {
if (!$this->isFilenameAllowed($f))
return false;
if (strtoupper(substr(PHP_OS, 0, 3)) == "WIN") {
// windows-specific limitations
foreach (['\\', '/', ':', '*', '?', '"', '<', '>', '|'] as $char)
if (strpos($f, $char) !== false)
return false;
} else {
// *nix-specific limitations
foreach (['/', '\0'] as $char)
if (strpos($f, $char) !== false)
return false;
}
// custom limitations
foreach ($this->config['forbiddenChars'] as $char)
if (strpos($f, $char) !== false)
return false;
return true;
}
private function isFilenameAllowed($f) {
if ($this->config['showhtdocs'] != 1 && substr($f, 0, 3) == ".ht")
return false;
elseif ($this->config['showhiddenfiles'] != 1 && substr($f, 0, 1) == ".")
return false;
elseif ($this->config['selfoverwrite'] != 1 && getcwd() == $this->initialWD && $f == basename(__FILE__))
return false;
else
return true;
}
// is cURL extention avaliable?
private function checkCurl() {
if (!function_exists("curl_init")
|| !function_exists("curl_setopt")
|| !function_exists("curl_exec")
|| !function_exists("curl_close")
)
return false;
else
return true;
}
/**
* This function checks the URL for potential SSRF attacks. Allowed is only
* http/ftp and only global IP addresses. You can disable the SSRF check in
* the configuration.
*/
public function checkUrlSsrf($url) {
if (!filter_var($url, FILTER_VALIDATE_URL))
return false;
$parts = parse_url($url);
if (!$parts)
return false;
// no host is not acceptable
if (!isset($parts['host']))
return false;
// other protocols than http(s) or ftp are not allowed (curl assumes http per default)
if (isset($parts['scheme']) && !in_array(strtolower($parts['scheme']), ['http', 'https', 'ftp']))
return false;
// if the host is no IP, resolve the hostname
$ips = [];
if (filter_var($parts['host'], FILTER_VALIDATE_IP))
array_push($ips, $parts['host']);
else
$ips = array_merge($ips, array_map(function($i) { return $i['ip'] ?? $i['ipv6']; }, dns_get_record($parts['host'], DNS_A + DNS_AAAA)));
if (empty($ips))
return false;
// check if any of the IPs is not global, if so then fail
foreach ($ips as $ip) {
if (version_compare(PHP_VERSION, '8.2.0') >= 0) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE)) {
return false;
}
} else {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE + FILTER_FLAG_NO_RES_RANGE)) {
return false;
}
}
}
return true;
}
private function fileDownload(array $options) {
if (!isset($options['name']) || trim($options['name']) == "")
$options['name'] = basename($options['file']);
if (isset($options['forceDL']) && $options['forceDL']) {
$content_type = "application/octet-stream";
header('Content-Disposition: attachment; filename="'.$options['name'].'"');
} else
$content_type = mime_content_type($options['file']);
header('Content-Type: '.$content_type);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.filesize($options['file']));
$file_stream = fopen($options['file'], 'rb');
$stdout_stream = fopen('php://output', 'wb');
stream_copy_to_stream($file_stream, $stdout_stream);
fclose($file_stream);
fclose($stdout_stream);
}
}
/**
* =======================================================================
* Improved File Manager
* ---------------------
* License: This project is provided under the terms of the MIT LICENSE
* http://github.com/misterunknown/ifm/blob/master/LICENSE
* =======================================================================
*
* archive class
*
* This class provides support for various archive types for the IFM. It can
* create and extract the following formats:
* * zip
* * tar
* * tar.gz
* * tar.bz2
*/
class IFMArchive {
/**
* Add a folder to an archive
*/
private static function addFolder(&$archive, $folder, $offset=0, $exclude_callback=null) {
if ($offset == 0)
$offset = strlen(dirname($folder)) + 1;
$archive->addEmptyDir(substr($folder, $offset));
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f != '.' && $f != '..') {
$filePath = $folder . '/' . $f;
if (file_exists($filePath) && is_readable($filePath)) {
if (is_file($filePath)) {
if (!is_callable($exclude_callback) || $exclude_callback($f))
$archive->addFile( $filePath, substr( $filePath, $offset ) );
} elseif (is_dir($filePath)) {
if (is_callable($exclude_callback))
self::addFolder($archive, $filePath, $offset, $exclude_callback);
else
self::addFolder($archive, $filePath, $offset);
}
}
}
}
closedir($handle);
}
/**
* Create a zip file
*/
public static function createZip($filenames, $archivename, $exclude_callback=null) {
$a = new ZipArchive();
$a->open($archivename, ZIPARCHIVE::CREATE);
if (!is_array($filenames))
$filenames = array($filenames);
foreach ($filenames as $f)
if (is_dir($f))
if (is_callable($exclude_callback))
self::addFolder( $a, $f, null, $exclude_callback );
else
self::addFolder( $a, $f );
elseif (is_file($f))
if (!is_callable($exclude_callback) || $exclude_callback($f))
$a->addFile($f, pathinfo($f, PATHINFO_BASENAME));
try {
return $a->close();
} catch (Exception $e) {
return false;
}
}
/**
* Unzip a zip file
*/
public static function extractZip($file, $destination="./") {
if (!file_exists($file))
return false;
$zip = new ZipArchive;
$res = $zip->open($file);
if ($res === true) {
$zip->extractTo($destination);
$zip->close();
return true;
} else
return false;
}
/**
* Creates a tar archive
*/
public static function createTar($filenames, $archivename, $format) {
$tmpf = $archivename . ".tar";
$a = new PharData($tmpf);
try {
if (!is_array($filenames))
$filenames = array($filenames);
foreach ($filenames as $f)
if (is_dir($f))
self::addFolder($a, $f);
elseif (is_file($f))
$a->addFile($f, pathinfo($f, PATHINFO_BASENAME));
switch ($format) {
case "tar.gz":
$a->compress(Phar::GZ);
@unlink($tmpf);
break;
case "tar.bz2":
$a->compress(Phar::BZ2);
@unlink($tmpf);
break;
}
return true;
} catch (Exception $e) {
@unlink($tmpf);
return false;
}
}
/**
* Extracts a tar archive
*/
public static function extractTar($file, $destination="./") {
if (!file_exists($file))
return false;
$tar = new PharData($file);
try {
$tar->extractTo($destination, null, true);
return true;
} catch (Exception $e) {
return false;
}
}
}
/**
* htpasswd parser
*/
class Htpasswd {
public $users = [];
public function __construct( $filename="" ) {
if( $filename )
$this->load( $filename );
}
/**
* Load a new htpasswd file
*/
public function load( $filename ) {
unset( $this->users );
if( file_exists( $filename ) && is_readable( $filename ) ) {
$lines = file( $filename );
foreach( $lines as $line ) {
list( $user, $pass ) = explode( ":", $line );
$this->users[$user] = trim( $pass );
}
return true;
} else
return false;
}
public function getUsers() {
return array_keys( $this->users );
}
public function userExist( $user ) {
return isset( $this->users[ $user ] );
}
public function verify( $user, $pass ) {
if( isset( $this->users[$user] ) ) {
return $this->verifyPassword( $pass, $this->users[$user] );
} else {
return false;
}
}
public function verifyPassword( $pass, $hash ) {
if( substr( $hash, 0, 4 ) == '$2y$' ) {
return password_verify( $pass, $hash );
} elseif( substr( $hash, 0, 6 ) == '$apr1$' ) {
$apr1 = new APR1_MD5();
return $apr1->check( $pass, $hash );
} elseif( substr( $hash, 0, 5 ) == '{SHA}' ) {
return base64_encode( sha1( $pass, TRUE ) ) == substr( $hash, 5 );
} else { // assume CRYPT
return crypt( $pass, $hash ) == $hash;
}
}
}
/**
* APR1_MD5 class
*
* Source: https://github.com/whitehat101/apr1-md5/blob/master/src/APR1_MD5.php
*/
class APR1_MD5 {
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const APRMD5_ALPHABET = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
// Source/References for core algorithm:
// http://www.cryptologie.net/article/126/bruteforce-apr1-hashes/
// http://svn.apache.org/viewvc/apr/apr-util/branches/1.3.x/crypto/apr_md5.c?view=co
// http://www.php.net/manual/en/function.crypt.php#73619
// http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
// Wikipedia
public static function hash($mdp, $salt = null) {
if (is_null($salt))
$salt = self::salt();
$salt = substr($salt, 0, 8);
$max = strlen($mdp);
$context = $mdp.'$apr1$'.$salt;
$binary = pack('H32', md5($mdp.$salt.$mdp));
for($i=$max; $i>0; $i-=16)
$context .= substr($binary, 0, min(16, $i));
for($i=$max; $i>0; $i>>=1)
$context .= ($i & 1) ? chr(0) : $mdp[0];
$binary = pack('H32', md5($context));
for($i=0; $i<1000; $i++) {
$new = ($i & 1) ? $mdp : $binary;
if($i % 3) $new .= $salt;
if($i % 7) $new .= $mdp;
$new .= ($i & 1) ? $binary : $mdp;
$binary = pack('H32', md5($new));
}
$hash = '';
for ($i = 0; $i < 5; $i++) {
$k = $i+6;
$j = $i+12;
if($j == 16) $j = 5;
$hash = $binary[$i].$binary[$k].$binary[$j].$hash;
}
$hash = chr(0).chr(0).$binary[11].$hash;
$hash = strtr(
strrev(substr(base64_encode($hash), 2)),
self::BASE64_ALPHABET,
self::APRMD5_ALPHABET
);
return '$apr1$'.$salt.'$'.$hash;
}
// 8 character salts are the best. Don't encourage anything but the best.
public static function salt() {
$alphabet = self::APRMD5_ALPHABET;
$salt = '';
for($i=0; $i<8; $i++) {
$offset = hexdec(bin2hex(openssl_random_pseudo_bytes(1))) % 64;
$salt .= $alphabet[$offset];
}
return $salt;
}
public static function check($plain, $hash) {
$parts = explode('$', $hash);
return self::hash($plain, $parts[2]) === $hash;
}
}
/**
* start IFM
*/
$ifm = new IFM();
$ifm->run();