Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
20.83% |
5 / 24 |
CRAP | |
48.94% |
138 / 282 |
filesystem | |
0.00% |
0 / 1 |
|
20.83% |
5 / 24 |
3926.02 | |
48.94% |
138 / 282 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
chgrp | |
0.00% |
0 / 1 |
2.02 | |
83.33% |
5 / 6 |
|||
chmod | |
0.00% |
0 / 1 |
31.34 | |
60.87% |
14 / 23 |
|||
chown | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
clean_path | |
100.00% |
1 / 1 |
8 | |
100.00% |
10 / 10 |
|||
copy | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
dump_file | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
exists | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
is_absolute_path | |
100.00% |
1 / 1 |
4 | |
100.00% |
1 / 1 |
|||
is_readable | |
0.00% |
0 / 1 |
56.00 | |
0.00% |
0 / 7 |
|||
is_writable | |
0.00% |
0 / 1 |
74.78 | |
35.71% |
5 / 14 |
|||
make_path_relative | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 1 |
|||
mirror | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
mkdir | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
phpbb_chmod | |
0.00% |
0 / 1 |
102.70 | |
60.00% |
33 / 55 |
|||
realpath | |
0.00% |
0 / 1 |
6.07 | |
87.50% |
7 / 8 |
|||
remove | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
rename | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
symlink | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
touch | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 6 |
|||
phpbb_is_writable | |
0.00% |
0 / 1 |
90.00 | |
0.00% |
0 / 13 |
|||
phpbb_own_realpath | |
0.00% |
0 / 1 |
16.55 | |
72.41% |
21 / 29 |
|||
to_iterator | |
100.00% |
1 / 1 |
3 | |
100.00% |
3 / 3 |
|||
resolve_path | |
0.00% |
0 / 1 |
104.11 | |
57.63% |
34 / 59 |
<?php | |
/** | |
* | |
* This file is part of the phpBB Forum Software package. | |
* | |
* @copyright (c) phpBB Limited <https://www.phpbb.com> | |
* @license GNU General Public License, version 2 (GPL-2.0) | |
* | |
* For full copyright and license information, please see | |
* the docs/CREDITS.txt file. | |
* | |
*/ | |
namespace phpbb\filesystem; | |
use phpbb\filesystem\exception\filesystem_exception; | |
/** | |
* A class with various functions that are related to paths, files and the filesystem | |
*/ | |
class filesystem implements filesystem_interface | |
{ | |
/** | |
* Store some information about file ownership for phpBB's chmod function | |
* | |
* @var array | |
*/ | |
protected $chmod_info; | |
/** | |
* Stores current working directory | |
* | |
* @var string|bool current working directory or false if it cannot be recovered | |
*/ | |
protected $working_directory; | |
/** | |
* Symfony's Filesystem component | |
* | |
* @var \Symfony\Component\Filesystem\Filesystem | |
*/ | |
protected $symfony_filesystem; | |
/** | |
* Constructor | |
*/ | |
public function __construct() | |
{ | |
$this->chmod_info = array(); | |
$this->symfony_filesystem = new \Symfony\Component\Filesystem\Filesystem(); | |
$this->working_directory = null; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function chgrp($files, $group, $recursive = false) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->chgrp($files, $group, $recursive); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
// Try to recover filename | |
// By the time this is written that is at the end of the message | |
$error = trim($e->getMessage()); | |
$file = substr($error, strrpos($error, ' ')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) | |
{ | |
if (is_null($perms)) | |
{ | |
// Default to read permission for compatibility reasons | |
$perms = self::CHMOD_READ; | |
} | |
// Check if we got a permission flag | |
if ($perms > self::CHMOD_ALL) | |
{ | |
$file_perm = $perms; | |
// Extract permissions | |
//$owner = ($file_perm >> 6) & 7; // This will be ignored | |
$group = ($file_perm >> 3) & 7; | |
$other = ($file_perm >> 0) & 7; | |
// Does any permissions provided? if so we add execute bit for directories | |
$group = ($group !== 0) ? ($group | self::CHMOD_EXECUTE) : $group; | |
$other = ($other !== 0) ? ($other | self::CHMOD_EXECUTE) : $other; | |
// Compute directory permissions | |
$dir_perm = (self::CHMOD_ALL << 6) + ($group << 3) + ($other << 3); | |
} | |
else | |
{ | |
// Add execute bit to owner if execute bit is among perms | |
$owner_perm = (self::CHMOD_READ | self::CHMOD_WRITE) | ($perms & self::CHMOD_EXECUTE); | |
$file_perm = ($owner_perm << 6) + ($perms << 3) + ($perms << 0); | |
// Compute directory permissions | |
$perm = ($perms !== 0) ? ($perms | self::CHMOD_EXECUTE) : $perms; | |
$dir_perm = (($owner_perm | self::CHMOD_EXECUTE) << 6) + ($perm << 3) + ($perm << 0); | |
} | |
// Symfony's filesystem component does not support extra execution flags on directories | |
// so we need to implement it again | |
foreach ($this->to_iterator($files) as $file) | |
{ | |
if ($recursive && is_dir($file) && !is_link($file)) | |
{ | |
$this->chmod(new \FilesystemIterator($file), $perms, true); | |
} | |
// Don't chmod links as mostly those require 0777 and that cannot be changed | |
if (is_dir($file) || (is_link($file) && $force_chmod_link)) | |
{ | |
if (true !== @chmod($file, $dir_perm)) | |
{ | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_PERMISSIONS', $file, array()); | |
} | |
} | |
else if (is_file($file)) | |
{ | |
if (true !== @chmod($file, $file_perm)) | |
{ | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_PERMISSIONS', $file, array()); | |
} | |
} | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function chown($files, $user, $recursive = false) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->chown($files, $user, $recursive); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
// Try to recover filename | |
// By the time this is written that is at the end of the message | |
$error = trim($e->getMessage()); | |
$file = substr($error, strrpos($error, ' ')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function clean_path($path) | |
{ | |
$exploded = explode('/', $path); | |
$filtered = array(); | |
foreach ($exploded as $part) | |
{ | |
if ($part === '.' && !empty($filtered)) | |
{ | |
continue; | |
} | |
if ($part === '..' && !empty($filtered) && $filtered[count($filtered) - 1] !== '.' && $filtered[count($filtered) - 1] !== '..') | |
{ | |
array_pop($filtered); | |
} | |
else | |
{ | |
$filtered[] = $part; | |
} | |
} | |
$path = implode('/', $filtered); | |
return $path; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function copy($origin_file, $target_file, $override = false) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->copy($origin_file, $target_file, $override); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
throw new filesystem_exception('FILESYSTEM_CANNOT_COPY_FILES', '', array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function dump_file($filename, $content) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->dumpFile($filename, $content); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
throw new filesystem_exception('FILESYSTEM_CANNOT_DUMP_FILE', $filename, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function exists($files) | |
{ | |
return $this->symfony_filesystem->exists($files); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function is_absolute_path($path) | |
{ | |
return (isset($path[0]) && $path[0] === '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function is_readable($files, $recursive = false) | |
{ | |
foreach ($this->to_iterator($files) as $file) | |
{ | |
if ($recursive && is_dir($file) && !is_link($file)) | |
{ | |
if (!$this->is_readable(new \FilesystemIterator($file), true)) | |
{ | |
return false; | |
} | |
} | |
if (!is_readable($file)) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function is_writable($files, $recursive = false) | |
{ | |
if (defined('PHP_WINDOWS_VERSION_MAJOR') || !function_exists('is_writable')) | |
{ | |
foreach ($this->to_iterator($files) as $file) | |
{ | |
if ($recursive && is_dir($file) && !is_link($file)) | |
{ | |
if (!$this->is_writable(new \FilesystemIterator($file), true)) | |
{ | |
return false; | |
} | |
} | |
if (!$this->phpbb_is_writable($file)) | |
{ | |
return false; | |
} | |
} | |
} | |
else | |
{ | |
// use built in is_writable | |
foreach ($this->to_iterator($files) as $file) | |
{ | |
if ($recursive && is_dir($file) && !is_link($file)) | |
{ | |
if (!$this->is_writable(new \FilesystemIterator($file), true)) | |
{ | |
return false; | |
} | |
} | |
if (!is_writable($file)) | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function make_path_relative($end_path, $start_path) | |
{ | |
return $this->symfony_filesystem->makePathRelative($end_path, $start_path); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array()) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->mirror($origin_dir, $target_dir, $iterator, $options); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
$msg = $e->getMessage(); | |
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_MIRROR_DIRECTORY', $filename, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function mkdir($dirs, $mode = 0777) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->mkdir($dirs, $mode); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
$msg = $e->getMessage(); | |
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CREATE_DIRECTORY', $filename, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function phpbb_chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) | |
{ | |
if (is_null($perms)) | |
{ | |
// Default to read permission for compatibility reasons | |
$perms = self::CHMOD_READ; | |
} | |
if (empty($this->chmod_info)) | |
{ | |
if (!function_exists('fileowner') || !function_exists('filegroup')) | |
{ | |
$this->chmod_info['process'] = false; | |
} | |
else | |
{ | |
$common_php_owner = @fileowner(__FILE__); | |
$common_php_group = @filegroup(__FILE__); | |
// And the owner and the groups PHP is running under. | |
$php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false; | |
$php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false; | |
// If we are unable to get owner/group, then do not try to set them by guessing | |
if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group) | |
{ | |
$this->chmod_info['process'] = false; | |
} | |
else | |
{ | |
$this->chmod_info = array( | |
'process' => true, | |
'common_owner' => $common_php_owner, | |
'common_group' => $common_php_group, | |
'php_uid' => $php_uid, | |
'php_gids' => $php_gids, | |
); | |
} | |
} | |
} | |
if ($this->chmod_info['process']) | |
{ | |
try | |
{ | |
foreach ($this->to_iterator($files) as $file) | |
{ | |
$file_uid = @fileowner($file); | |
$file_gid = @filegroup($file); | |
// Change owner | |
if (is_writable($file) && $file_uid !== $this->chmod_info['common_owner']) | |
{ | |
$this->chown($file, $this->chmod_info['common_owner'], $recursive); | |
} | |
// Change group | |
if (is_writable($file) && $file_gid !== $this->chmod_info['common_group']) | |
{ | |
$this->chgrp($file, $this->chmod_info['common_group'], $recursive); | |
} | |
clearstatcache(); | |
$file_uid = @fileowner($file); | |
$file_gid = @filegroup($file); | |
} | |
} | |
catch (filesystem_exception $e) | |
{ | |
$this->chmod_info['process'] = false; | |
} | |
} | |
// Still able to process? | |
if ($this->chmod_info['process']) | |
{ | |
if ($file_uid === $this->chmod_info['php_uid']) | |
{ | |
$php = 'owner'; | |
} | |
else if (in_array($file_gid, $this->chmod_info['php_gids'])) | |
{ | |
$php = 'group'; | |
} | |
else | |
{ | |
// Since we are setting the everyone bit anyway, no need to do expensive operations | |
$this->chmod_info['process'] = false; | |
} | |
} | |
// We are not able to determine or change something | |
if (!$this->chmod_info['process']) | |
{ | |
$php = 'other'; | |
} | |
switch ($php) | |
{ | |
case 'owner': | |
try | |
{ | |
$this->chmod($files, $perms, $recursive, $force_chmod_link); | |
clearstatcache(); | |
if ($this->is_readable($files) && $this->is_writable($files)) | |
{ | |
break; | |
} | |
} | |
catch (filesystem_exception $e) | |
{ | |
// Do nothing | |
} | |
case 'group': | |
try | |
{ | |
$this->chmod($files, $perms, $recursive, $force_chmod_link); | |
clearstatcache(); | |
if ((!($perms & self::CHMOD_READ) || $this->is_readable($files, $recursive)) && (!($perms & self::CHMOD_WRITE) || $this->is_writable($files, $recursive))) | |
{ | |
break; | |
} | |
} | |
catch (filesystem_exception $e) | |
{ | |
// Do nothing | |
} | |
case 'other': | |
default: | |
$this->chmod($files, $perms, $recursive, $force_chmod_link); | |
break; | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function realpath($path) | |
{ | |
if (!function_exists('realpath')) | |
{ | |
return $this->phpbb_own_realpath($path); | |
} | |
$realpath = realpath($path); | |
// Strangely there are provider not disabling realpath but returning strange values. :o | |
// We at least try to cope with them. | |
if ((!$this->is_absolute_path($path) && $realpath === $path) || $realpath === false) | |
{ | |
return $this->phpbb_own_realpath($path); | |
} | |
// Check for DIRECTORY_SEPARATOR at the end (and remove it!) | |
if (substr($realpath, -1) === DIRECTORY_SEPARATOR) | |
{ | |
$realpath = substr($realpath, 0, -1); | |
} | |
return $realpath; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function remove($files) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->remove($files); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
// Try to recover filename | |
// By the time this is written that is at the end of the message | |
$error = trim($e->getMessage()); | |
$file = substr($error, strrpos($error, ' ')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_DELETE_FILES', $file, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function rename($origin, $target, $overwrite = false) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->rename($origin, $target, $overwrite); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
$msg = $e->getMessage(); | |
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_RENAME_FILE', $filename, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function symlink($origin_dir, $target_dir, $copy_on_windows = false) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->symlink($origin_dir, $target_dir, $copy_on_windows); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
throw new filesystem_exception('FILESYSTEM_CANNOT_CREATE_SYMLINK', $origin_dir, array(), $e); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function touch($files, $time = null, $access_time = null) | |
{ | |
try | |
{ | |
$this->symfony_filesystem->touch($files, $time, $access_time); | |
} | |
catch (\Symfony\Component\Filesystem\Exception\IOException $e) | |
{ | |
// Try to recover filename | |
// By the time this is written that is at the end of the message | |
$error = trim($e->getMessage()); | |
$file = substr($error, strrpos($error, ' ')); | |
throw new filesystem_exception('FILESYSTEM_CANNOT_TOUCH_FILES', $file, array(), $e); | |
} | |
} | |
/** | |
* phpBB's implementation of is_writable | |
* | |
* @todo Investigate if is_writable is still buggy | |
* | |
* @param string $file file/directory to check if writable | |
* | |
* @return bool true if the given path is writable | |
*/ | |
protected function phpbb_is_writable($file) | |
{ | |
if (file_exists($file)) | |
{ | |
// Canonicalise path to absolute path | |
$file = $this->realpath($file); | |
if (is_dir($file)) | |
{ | |
// Test directory by creating a file inside the directory | |
$result = @tempnam($file, 'i_w'); | |
if (is_string($result) && file_exists($result)) | |
{ | |
unlink($result); | |
// Ensure the file is actually in the directory (returned realpathed) | |
return (strpos($result, $file) === 0) ? true : false; | |
} | |
} | |
else | |
{ | |
$handle = new \SplFileInfo($file); | |
// Returns TRUE if writable, FALSE otherwise | |
return $handle->isWritable(); | |
} | |
} | |
else | |
{ | |
// file does not exist test if we can write to the directory | |
$dir = dirname($file); | |
if (file_exists($dir) && is_dir($dir) && $this->phpbb_is_writable($dir)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Try to resolve real path when PHP's realpath fails to do so | |
* | |
* @param string $path | |
* @return bool|string | |
*/ | |
protected function phpbb_own_realpath($path) | |
{ | |
// Replace all directory separators with '/' | |
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path); | |
$is_absolute_path = false; | |
$path_prefix = ''; | |
if ($this->is_absolute_path($path)) | |
{ | |
$is_absolute_path = true; | |
} | |
else | |
{ | |
// Resolve working directory and store it | |
if (is_null($this->working_directory)) | |
{ | |
if (function_exists('getcwd')) | |
{ | |
$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()); | |
} | |
// | |
// From this point on we really just guessing | |
// If chdir were called we screwed | |
// | |
else if (function_exists('debug_backtrace')) | |
{ | |
$call_stack = debug_backtrace(0); | |
$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', dirname($call_stack[count($call_stack) - 1]['file'])); | |
} | |
else | |
{ | |
// | |
// Assuming that the working directory is phpBB root | |
// we could use this as a fallback, when phpBB will use controllers | |
// everywhere this will be a safe assumption | |
// | |
//$dir_parts = explode(DIRECTORY_SEPARATOR, __DIR__); | |
//$namespace_parts = explode('\\', trim(__NAMESPACE__, '\\')); | |
//$namespace_part_count = count($namespace_parts); | |
// Check if we still loading from root | |
//if (array_slice($dir_parts, -$namespace_part_count) === $namespace_parts) | |
//{ | |
// $this->working_directory = implode('/', array_slice($dir_parts, 0, -$namespace_part_count)); | |
//} | |
//else | |
//{ | |
// $this->working_directory = false; | |
//} | |
$this->working_directory = false; | |
} | |
} | |
if ($this->working_directory !== false) | |
{ | |
$is_absolute_path = true; | |
$path = $this->working_directory . '/' . $path; | |
} | |
} | |
if ($is_absolute_path) | |
{ | |
if (defined('PHP_WINDOWS_VERSION_MAJOR')) | |
{ | |
$path_prefix = $path[0] . ':'; | |
$path = substr($path, 2); | |
} | |
else | |
{ | |
$path_prefix = ''; | |
} | |
} | |
$resolved_path = $this->resolve_path($path, $path_prefix, $is_absolute_path); | |
if ($resolved_path === false) | |
{ | |
return false; | |
} | |
if (!@file_exists($resolved_path) || (!@is_dir($resolved_path . '/') && !is_file($resolved_path))) | |
{ | |
return false; | |
} | |
// Return OS specific directory separators | |
$resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved_path); | |
// Check for DIRECTORY_SEPARATOR at the end (and remove it!) | |
if (substr($resolved, -1) === DIRECTORY_SEPARATOR) | |
{ | |
return substr($resolved, 0, -1); | |
} | |
return $resolved; | |
} | |
/** | |
* Convert file(s) to \Traversable object | |
* | |
* This is the same function as Symfony's toIterator, but that is private | |
* so we cannot use it. | |
* | |
* @param string|array|\Traversable $files filename/list of filenames | |
* @return \Traversable | |
*/ | |
protected function to_iterator($files) | |
{ | |
if (!$files instanceof \Traversable) | |
{ | |
$files = new \ArrayObject(is_array($files) ? $files : array($files)); | |
} | |
return $files; | |
} | |
/** | |
* Try to resolve symlinks in path | |
* | |
* @param string $path The path to resolve | |
* @param string $prefix The path prefix (on windows the drive letter) | |
* @param bool $absolute Whether or not the path is absolute | |
* @param bool $return_array Whether or not to return path parts | |
* | |
* @return string|array|bool returns the resolved path or an array of parts of the path if $return_array is true | |
* or false if path cannot be resolved | |
*/ | |
protected function resolve_path($path, $prefix = '', $absolute = false, $return_array = false) | |
{ | |
if ($return_array) | |
{ | |
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path); | |
} | |
trim ($path, '/'); | |
$path_parts = explode('/', $path); | |
$resolved = array(); | |
$resolved_path = $prefix; | |
$file_found = false; | |
foreach ($path_parts as $path_part) | |
{ | |
if ($file_found) | |
{ | |
return false; | |
} | |
if (empty($path_part) || ($path_part === '.' && ($absolute || !empty($resolved)))) | |
{ | |
continue; | |
} | |
else if ($absolute && $path_part === '..') | |
{ | |
if (empty($resolved)) | |
{ | |
// No directories above root | |
return false; | |
} | |
array_pop($resolved); | |
$resolved_path = false; | |
} | |
else if ($path_part === '..' && !empty($resolved) && !in_array($resolved[count($resolved) - 1], array('.', '..'))) | |
{ | |
array_pop($resolved); | |
$resolved_path = false; | |
} | |
else | |
{ | |
if ($resolved_path === false) | |
{ | |
if (empty($resolved)) | |
{ | |
$resolved_path = ($absolute) ? $prefix . '/' . $path_part : $path_part; | |
} | |
else | |
{ | |
$tmp_array = $resolved; | |
if ($absolute) | |
{ | |
array_unshift($tmp_array, $prefix); | |
} | |
$resolved_path = implode('/', $tmp_array); | |
} | |
} | |
$current_path = $resolved_path . '/' . $path_part; | |
// Resolve symlinks | |
if (@is_link($current_path)) | |
{ | |
if (!function_exists('readlink')) | |
{ | |
return false; | |
} | |
$link = readlink($current_path); | |
// Is link has an absolute path in it? | |
if ($this->is_absolute_path($link)) | |
{ | |
if (defined('PHP_WINDOWS_VERSION_MAJOR')) | |
{ | |
$prefix = $link[0] . ':'; | |
$link = substr($link, 2); | |
} | |
else | |
{ | |
$prefix = ''; | |
} | |
$resolved = $this->resolve_path($link, $prefix, true, true); | |
$absolute = true; | |
} | |
else | |
{ | |
$resolved = $this->resolve_path($resolved_path . '/' . $link, $prefix, $absolute, true); | |
} | |
if (!$resolved) | |
{ | |
return false; | |
} | |
$resolved_path = false; | |
} | |
else if (@is_dir($current_path . '/')) | |
{ | |
$resolved[] = $path_part; | |
$resolved_path = $current_path; | |
} | |
else if (@is_file($current_path)) | |
{ | |
$resolved[] = $path_part; | |
$resolved_path = $current_path; | |
$file_found = true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
} | |
// If at the end of the path there were a .. or . | |
// we need to build the path again. | |
// Only doing this when a string is expected in return | |
if ($resolved_path === false && $return_array === false) | |
{ | |
if (empty($resolved)) | |
{ | |
$resolved_path = ($absolute) ? $prefix . '/' : './'; | |
} | |
else | |
{ | |
$tmp_array = $resolved; | |
if ($absolute) | |
{ | |
array_unshift($tmp_array, $prefix); | |
} | |
$resolved_path = implode('/', $tmp_array); | |
} | |
} | |
return ($return_array) ? $resolved : $resolved_path; | |
} | |
} |