<?php
// 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
{
private $callback;
public function __construct(RecursiveIterator $iterator, $callback)
{
$this->callback = $callback;
parent::__construct($iterator);
}
public function accept()
{
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
* @author Troex Nevelin
**/
class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
{
/**
* Driver id
* Must be started from letter and contains [a-z0-9]
* Used as part of volume id
*
* @var string
**/
protected $driverId = 'l';
/**
* Required to count total archive files size
*
* @var int
**/
protected $archiveSize = 0;
/**
* Is checking stat owner
*
* @var boolean
*/
protected $statOwner = false;
/**
* Path to quarantine directory
*
* @var string
*/
private $quarantine;
/**
* Constructor
* 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 === '/') {
// Linux
$this->options['acceptedName'] = '/^[^\.\/\x00][^\/\x00]*$/';
} else {
// Windows
$this->options['acceptedName'] = '/^[^\.\/\x00\\\:*?"<>|][^\/\x00\\\:*?"<>|]*$/';
}
}
/*********************************************************************/
/* INIT AND CONFIGURE */
/*********************************************************************/
/**
* Prepare driver before mount volume.
* Return true if volume is ready.
*
* @return bool
**/
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'] = '';
}
}
if (!$cwd = getcwd()) {
return $this->setError('elFinder LocalVolumeDriver requires a result of getcwd().');
}
// detect systemRoot
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');
}
// check 'statCorrector'
if (empty($this->options['statCorrector']) || !is_callable($this->options['statCorrector'])) {
$this->options['statCorrector'] = null;
}
return true;
}
/**
* Configure after successfull mount.
*
* @return void
* @throws elFinderAbortException
* @author Dmitry (dio) Levashov
*/
protected function configure()
{
$hiddens = array();
$root = $this->stat($this->root);
// check thumbnails path
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']);
} else {
$this->options['tmbPath'] = $this->_normpath($this->options['tmbPath']);
}
}
// check temp path
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']);
} else {
$this->options['tmpPath'] = $this->_normpath($this->options['tmpPath']);
}
}
// check quarantine path
$_quarantine = '';
if (!empty($this->options['quarantine'])) {
if (strpos($this->options['quarantine'], DIRECTORY_SEPARATOR) === false) {
$_quarantine = $this->_abspath($this->options['quarantine']);
$this->options['quarantine'] = '';
} else {
$this->options['quarantine'] = $this->_normpath($this->options['quarantine']);
}
} else {
$_quarantine = $this->_abspath('.quarantine');
}
is_dir($_quarantine) && self::localRmdirRecursive($_quarantine);
parent::configure();
// check tmbPath
if (!$this->tmbPath && isset($hiddens['tmb'])) {
unset($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)) {
$this->tmbURL .= '/';
}
}
}
// set $this->tmp by options['tmpPath']
$this->tmp = '';
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'];
} else {
if (isset($hiddens['temp'])) {
unset($hiddens['temp']);
}
}
}
if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
$this->tmp = $tmp;
}
// check quarantine dir
$this->quarantine = '';
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'];
} else {
if (isset($hiddens['quarantine'])) {
unset($hiddens['quarantine']);
}
}
} else if ($_path = elFinder::getCommonTempPath()) {
$this->quarantine = $_path;
}
if (!$this->quarantine) {
if (!$this->tmp) {
$this->archivers['extract'] = array();
$this->disabled[] = 'extract';
} else {
$this->quarantine = $this->tmp;
}
}
if ($hiddens) {
foreach ($hiddens as $hidden) {
$this->attributes[] = array(
'pattern' => '~^' . preg_quote(DIRECTORY_SEPARATOR . $hidden, '~') . '$~',
'read' => false,
'write' => false,
'locked' => true,
'hidden' => true
);
}
}
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
*
* @param string $path
* @param int $standby
* @param number $compare
*
* @return number|bool
* @throws elFinderAbortException
*/
public function localFileSystemInotify($path, $standby, $compare)
{
if (isset($this->sessionCache['localFileSystemInotify_disable'])) {
return false;
}
$path = realpath($path);
$mtime = filemtime($path);
if (!$mtime) {
return false;
}
if ($mtime != $compare) {
return $mtime;
}
$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 ($r === 0) {
// changed
clearstatcache();
if (file_exists($path)) {
$mtime = filemtime($path); // error on busy?
return $mtime ? $mtime : time();
} else {
// target was removed
return 0;
}
} else if ($r === 2) {
// not changed (timeout)
return $compare;
}
// error
// cache to $_SESSION
$this->sessionCache['localFileSystemInotify_disable'] = true;
$this->session->set($this->id, $this->sessionCache);
return false;
}
/*********************************************************************/
/* FS API */
/*********************************************************************/
/*********************** paths/urls *************************/
/**
* Return parent directory path
*
* @param string $path file path
*
* @return string
* @author Dmitry (dio) Levashov
**/
protected function _dirname($path)
{
return dirname($path);
}
/**
* Return file name
*
* @param string $path file path
*
* @return string
* @author Dmitry (dio) Levashov
**/
protected function _basename($path)
{
return basename($path);
}
/**
* Join dir name and file name and retur full path
*
* @param string $dir
* @param string $name
*
* @return string
* @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) == '..') {
$dir = $this->root;
}
if (strpos($name, '..' . DIRECTORY_SEPARATOR) !== false) {
$name = basename($name);
}
$path = $dir . DIRECTORY_SEPARATOR . $name;
}
return $path;
}
/**
* Return normalized path, this works the same as os.path.normpath() in Python
*
* @param string $path path
*
* @return string
* @author Troex Nevelin
**/
protected function _normpath($path)
{
if (empty($path)) {
return '.';
}
$changeSep = (DIRECTORY_SEPARATOR !== '/');
if ($changeSep) {
$drive = '';
if (preg_match('/^([a-zA-Z]:)(.*)/', $path, $m)) {
$drive = $m[1];
$path = $m[2] ? $m[2] : '/';
}
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
if (strpos($path, '/') === 0) {
$initial_slashes = true;
} else {
$initial_slashes = false;
}
if (($initial_slashes)
&& (strpos($path, '//') === 0)
&& (strpos($path, '///') === false)) {
$initial_slashes = 2;
}
$initial_slashes = (int)$initial_slashes;
$comps = explode('/', $path);
$new_comps = array();
foreach ($comps as $comp) {
if (in_array($comp, array('', '.'))) {
continue;
}
if (($comp != '..')
|| (!$initial_slashes && !$new_comps)
|| ($new_comps && (end($new_comps) == '..'))) {
array_push($new_comps, $comp);
} elseif ($new_comps) {
array_pop($new_comps);
}
}
$comps = $new_comps;
$path = implode('/', $comps);
if ($initial_slashes) {
$path = str_repeat('/', $initial_slashes) . $path;
}
if ($changeSep) {
$path = $drive . str_replace('/', DIRECTORY_SEPARATOR, $path);
}
return $path ? $path : '.';
}
/**
* Return file path related to root dir
*
* @param string $path file path
*
* @return string
* @author Dmitry (dio) Levashov
**/
protected function _relpath($path)
{
if ($path === $this->root) {
return '';
} else {
if (strpos($path, $this->root) === 0) {
return ltrim(substr($path, strlen($this->root)), DIRECTORY_SEPARATOR);
} else {
// for link
return $path;
}
}
}
/**
* Convert path related to root dir into real path
*
* @param string $path file path
*
* @return string
* @author Dmitry (dio) Levashov
**/
protected function _abspath($path)
{
if ($path === DIRECTORY_SEPARATOR) {