0x1949 Team - FAZEMRX - MANAGER
Edit File: filesystem.php
<?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; } }