// Implement similar functionality in PHP 5.2 or 5.3
// http://php.net/manual/class.recursivecallbackfilteriterator.php#110974
if (!class_exists('RecursiveCallbackFilterIterator', false)) {
class RecursiveCallbackFilterIterator extends RecursiveFilterIterator
public function __construct(RecursiveIterator $iterator, $callback)
$this->callback = $callback;
parent::__construct($iterator);
return call_user_func($this->callback, parent::current(), parent::key(), parent::getInnerIterator());
public function getChildren()
return new self($this->getInnerIterator()->getChildren(), $this->callback);
* elFinder driver for local filesystem.
* @author Dmitry (dio) Levashov
class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
* Must be started from letter and contains [a-z0-9]
* Used as part of volume id
protected $driverId = 'l';
* Required to count total archive files size
protected $archiveSize = 0;
protected $statOwner = false;
* Path to quarantine directory
* Extend options with required fields
* @author Dmitry (dio) Levashov
public function __construct()
$this->options['alias'] = ''; // alias to replace root dir name
$this->options['dirMode'] = 0755; // new dirs mode
$this->options['fileMode'] = 0644; // new files mode
$this->options['rootCssClass'] = 'elfinder-navbar-root-local';
$this->options['followSymLinks'] = true;
$this->options['detectDirIcon'] = ''; // file name that is detected as a folder icon e.g. '.diricon.png'
$this->options['keepTimestamp'] = array('copy', 'move'); // keep timestamp at inner filesystem allowed 'copy', 'move' and 'upload'
$this->options['substituteImg'] = true; // support substitute image with dim command
$this->options['statCorrector'] = null; // callable to correct stat data `function(&$stat, $path, $statOwner, $volumeDriveInstance){}`
if (DIRECTORY_SEPARATOR === '/') {
$this->options['acceptedName'] = '/^[^\.\/\x00][^\/\x00]*$/';
$this->options['acceptedName'] = '/^[^\.\/\x00\\\:*?"<>|][^\/\x00\\\:*?"<>|]*$/';
/*********************************************************************/
/*********************************************************************/
* Prepare driver before mount volume.
* Return true if volume is ready.
protected function init()
// Normalize directory separator for windows
if (DIRECTORY_SEPARATOR !== '/') {
foreach (array('path', 'tmbPath', 'tmpPath', 'quarantine') as $key) {
if (!empty($this->options[$key])) {
$this->options[$key] = str_replace('/', DIRECTORY_SEPARATOR, $this->options[$key]);
// PHP >= 7.1 Supports UTF-8 path on Windows
if (version_compare(PHP_VERSION, '7.1', '>=')) {
$this->options['encoding'] = '';
$this->options['locale'] = '';
return $this->setError('elFinder LocalVolumeDriver requires a result of getcwd().');
if (!isset($this->options['systemRoot'])) {
if ($cwd[0] === DIRECTORY_SEPARATOR || $this->root[0] === DIRECTORY_SEPARATOR) {
$this->systemRoot = DIRECTORY_SEPARATOR;
} else if (preg_match('/^([a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . ')/', $this->root, $m)) {
$this->systemRoot = $m[1];
} else if (preg_match('/^([a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . ')/', $cwd, $m)) {
$this->systemRoot = $m[1];
$this->root = $this->getFullPath($this->root, $cwd);
if (!empty($this->options['startPath'])) {
$this->options['startPath'] = $this->getFullPath($this->options['startPath'], $this->root);
if (is_null($this->options['syncChkAsTs'])) {
$this->options['syncChkAsTs'] = true;
if (is_null($this->options['syncCheckFunc'])) {
$this->options['syncCheckFunc'] = array($this, 'localFileSystemInotify');
if (empty($this->options['statCorrector']) || !is_callable($this->options['statCorrector'])) {
$this->options['statCorrector'] = null;
* Configure after successfull mount.
* @throws elFinderAbortException
* @author Dmitry (dio) Levashov
protected function configure()
$root = $this->stat($this->root);
if (!empty($this->options['tmbPath'])) {
if (strpos($this->options['tmbPath'], DIRECTORY_SEPARATOR) === false) {
$hiddens['tmb'] = $this->options['tmbPath'];
$this->options['tmbPath'] = $this->_abspath($this->options['tmbPath']);
$this->options['tmbPath'] = $this->_normpath($this->options['tmbPath']);
if (!empty($this->options['tmpPath'])) {
if (strpos($this->options['tmpPath'], DIRECTORY_SEPARATOR) === false) {
$hiddens['temp'] = $this->options['tmpPath'];
$this->options['tmpPath'] = $this->_abspath($this->options['tmpPath']);
$this->options['tmpPath'] = $this->_normpath($this->options['tmpPath']);
if (!empty($this->options['quarantine'])) {
if (strpos($this->options['quarantine'], DIRECTORY_SEPARATOR) === false) {
$_quarantine = $this->_abspath($this->options['quarantine']);
$this->options['quarantine'] = '';
$this->options['quarantine'] = $this->_normpath($this->options['quarantine']);
$_quarantine = $this->_abspath('.quarantine');
is_dir($_quarantine) && self::localRmdirRecursive($_quarantine);
if (!$this->tmbPath && isset($hiddens['tmb'])) {
// if no thumbnails url - try detect it
if ($root['read'] && !$this->tmbURL && $this->URL) {
if (strpos($this->tmbPath, $this->root) === 0) {
$this->tmbURL = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($this->tmbPath, strlen($this->root) + 1));
if (preg_match("|[^/?&=]$|", $this->tmbURL)) {
// set $this->tmp by options['tmpPath']
if (!empty($this->options['tmpPath'])) {
if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], $this->options['dirMode'], true)) && is_writable($this->options['tmpPath'])) {
$this->tmp = $this->options['tmpPath'];
if (isset($hiddens['temp'])) {
if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
if (!empty($this->options['quarantine'])) {
if ((is_dir($this->options['quarantine']) || mkdir($this->options['quarantine'], $this->options['dirMode'], true)) && is_writable($this->options['quarantine'])) {
$this->quarantine = $this->options['quarantine'];
if (isset($hiddens['quarantine'])) {
unset($hiddens['quarantine']);
} else if ($_path = elFinder::getCommonTempPath()) {
$this->quarantine = $_path;
if (!$this->quarantine) {
$this->archivers['extract'] = array();
$this->disabled[] = 'extract';
$this->quarantine = $this->tmp;
foreach ($hiddens as $hidden) {
$this->attributes[] = array(
'pattern' => '~^' . preg_quote(DIRECTORY_SEPARATOR . $hidden, '~') . '$~',
if (!empty($this->options['keepTimestamp'])) {
$this->options['keepTimestamp'] = array_flip($this->options['keepTimestamp']);
$this->statOwner = (!empty($this->options['statOwner']));
// enable WinRemoveTailDots plugin on Windows server
if (DIRECTORY_SEPARATOR !== '/') {
if (!isset($this->options['plugin'])) {
$this->options['plugin'] = array();
$this->options['plugin']['WinRemoveTailDots'] = array('enable' => true);
* Long pooling sync checker
* This function require server command `inotifywait`
* If `inotifywait` need full path, Please add `define('ELFINER_INOTIFYWAIT_PATH', '/PATH_TO/inotifywait');` into connector.php
* @throws elFinderAbortException
public function localFileSystemInotify($path, $standby, $compare)
if (isset($this->sessionCache['localFileSystemInotify_disable'])) {
$mtime = filemtime($path);
if ($mtime != $compare) {
$inotifywait = defined('ELFINER_INOTIFYWAIT_PATH') ? ELFINER_INOTIFYWAIT_PATH : 'inotifywait';
$standby = max(1, intval($standby));
$cmd = $inotifywait . ' ' . escapeshellarg($path) . ' -t ' . $standby . ' -e moved_to,moved_from,move,close_write,delete,delete_self';
$this->procExec($cmd, $o, $r);
if (file_exists($path)) {
$mtime = filemtime($path); // error on busy?
return $mtime ? $mtime : time();
$this->sessionCache['localFileSystemInotify_disable'] = true;
$this->session->set($this->id, $this->sessionCache);
/*********************************************************************/
/*********************************************************************/
/*********************** paths/urls *************************/
* Return parent directory path
* @param string $path file path
* @author Dmitry (dio) Levashov
protected function _dirname($path)
* @param string $path file path
* @author Dmitry (dio) Levashov
protected function _basename($path)
* Join dir name and file name and retur full path
* @author Dmitry (dio) Levashov
protected function _joinPath($dir, $name)
$dir = rtrim($dir, DIRECTORY_SEPARATOR);
$path = realpath($dir . DIRECTORY_SEPARATOR . $name);
// realpath() returns FALSE if the file does not exist
if ($path === false || strpos($path, $this->root) !== 0) {
if (DIRECTORY_SEPARATOR !== '/') {
$dir = str_replace('/', DIRECTORY_SEPARATOR, $dir);
$name = str_replace('/', DIRECTORY_SEPARATOR, $name);
// Directory traversal measures
if (strpos($dir, '..' . DIRECTORY_SEPARATOR) !== false || substr($dir, -2) == '..') {
if (strpos($name, '..' . DIRECTORY_SEPARATOR) !== false) {
$path = $dir . DIRECTORY_SEPARATOR . $name;
* Return normalized path, this works the same as os.path.normpath() in Python
* @param string $path path
protected function _normpath($path)
$changeSep = (DIRECTORY_SEPARATOR !== '/');
if (preg_match('/^([a-zA-Z]:)(.*)/', $path, $m)) {
$path = $m[2] ? $m[2] : '/';
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
if (strpos($path, '/') === 0) {
$initial_slashes = false;
&& (strpos($path, '//') === 0)
&& (strpos($path, '///') === false)) {
$initial_slashes = (int)$initial_slashes;
$comps = explode('/', $path);
foreach ($comps as $comp) {
if (in_array($comp, array('', '.'))) {
|| (!$initial_slashes && !$new_comps)
|| ($new_comps && (end($new_comps) == '..'))) {
array_push($new_comps, $comp);
$path = implode('/', $comps);
$path = str_repeat('/', $initial_slashes) . $path;
$path = $drive . str_replace('/', DIRECTORY_SEPARATOR, $path);
return $path ? $path : '.';
* Return file path related to root dir
* @param string $path file path
* @author Dmitry (dio) Levashov
protected function _relpath($path)
if ($path === $this->root) {
if (strpos($path, $this->root) === 0) {
return ltrim(substr($path, strlen($this->root)), DIRECTORY_SEPARATOR);
* Convert path related to root dir into real path
* @param string $path file path
* @author Dmitry (dio) Levashov
protected function _abspath($path)
if ($path === DIRECTORY_SEPARATOR) {
$path = $this->_normpath($path);
if (strpos($path, $this->systemRoot) === 0) {
} else if (DIRECTORY_SEPARATOR !== '/' && preg_match('/^[a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . '/', $path)) {
return $this->_joinPath($this->root, $path);