primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,186 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
/**
* Generic Buffer stream handler
*
* This class provides a generic buffer stream. It can be used to store/retrieve/manipulate
* string buffers with the standard PHP filesystem I/O methods.
*
* @since 1.0
*/
class Buffer
{
/**
* Stream position
*
* @var integer
* @since 1.0
*/
public $position = 0;
/**
* Buffer name
*
* @var string
* @since 1.0
*/
public $name;
/**
* Buffer hash
*
* @var array
* @since 1.0
*/
public $buffers = [];
/**
* Function to open file or url
*
* @param string $path The URL that was passed
* @param string $mode Mode used to open the file @see fopen
* @param integer $options Flags used by the API, may be STREAM_USE_PATH and STREAM_REPORT_ERRORS
* @param string $openedPath Full path of the resource. Used with STREAN_USE_PATH option
*
* @return boolean
*
* @since 1.0
* @see streamWrapper::stream_open
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
$url = parse_url($path);
$this->name = $url['host'];
$this->buffers[$this->name] = null;
$this->position = 0;
return true;
}
/**
* Read stream
*
* @param integer $count How many bytes of data from the current position should be returned.
*
* @return mixed The data from the stream up to the specified number of bytes (all data if
* the total number of bytes in the stream is less than $count. Null if
* the stream is empty.
*
* @see streamWrapper::stream_read
* @since 1.0
*/
public function stream_read($count)
{
$ret = substr($this->buffers[$this->name], $this->position, $count);
$this->position += \strlen($ret);
return $ret;
}
/**
* Write stream
*
* @param string $data The data to write to the stream.
*
* @return integer
*
* @see streamWrapper::stream_write
* @since 1.0
*/
public function stream_write($data)
{
$left = substr($this->buffers[$this->name], 0, $this->position);
$right = substr($this->buffers[$this->name], $this->position + \strlen($data));
$this->buffers[$this->name] = $left . $data . $right;
$this->position += \strlen($data);
return \strlen($data);
}
/**
* Function to get the current position of the stream
*
* @return integer
*
* @see streamWrapper::stream_tell
* @since 1.0
*/
public function stream_tell()
{
return $this->position;
}
/**
* Function to test for end of file pointer
*
* @return boolean True if the pointer is at the end of the stream
*
* @see streamWrapper::stream_eof
* @since 1.0
*/
public function stream_eof()
{
return $this->position >= \strlen($this->buffers[$this->name]);
}
/**
* The read write position updates in response to $offset and $whence
*
* @param integer $offset The offset in bytes
* @param integer $whence Position the offset is added to
* Options are SEEK_SET, SEEK_CUR, and SEEK_END
*
* @return boolean True if updated
*
* @see streamWrapper::stream_seek
* @since 1.0
*/
public function stream_seek($offset, $whence)
{
switch ($whence) {
case \SEEK_SET:
if ($offset < \strlen($this->buffers[$this->name]) && $offset >= 0) {
$this->position = $offset;
return true;
}
return false;
case \SEEK_CUR:
if ($offset >= 0) {
$this->position += $offset;
return true;
}
return false;
case \SEEK_END:
if (\strlen($this->buffers[$this->name]) + $offset >= 0) {
$this->position = \strlen($this->buffers[$this->name]) + $offset;
return true;
}
return false;
default:
return false;
}
}
}
// Register the stream
// phpcs:disable PSR1.Files.SideEffects
stream_wrapper_register('buffer', 'Joomla\\Filesystem\\Buffer');
// phpcs:enable PSR1.Files.SideEffects

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem\Exception;
use Joomla\Filesystem\Path;
/**
* Exception class for handling errors in the Filesystem package
*
* @since 1.2.0
* @change 2.0.1 If the message contains a full path, the root path (JPATH_ROOT) is removed from it
* to avoid any full path disclosure. Before 2.0.1, the path was propagated as provided.
*/
class FilesystemException extends \RuntimeException
{
/**
* Constructor.
*
* @param string $message The message
* @param integer $code The code
* @param \Throwable|null $previous A previous exception
*/
public function __construct($message = "", $code = 0, ?\Throwable $previous = null)
{
parent::__construct(
Path::removeRoot($message),
$code,
$previous
);
}
}

View File

@ -0,0 +1,341 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
use Joomla\Filesystem\Exception\FilesystemException;
/**
* A File handling class
*
* @since 1.0
*/
class File
{
/**
* Gets the extension of a file name
*
* @param string $file The file name
*
* @return string The file extension
*
* @since 3.0.0
*/
public static function getExt($file)
{
// String manipulation should be faster than pathinfo() on newer PHP versions.
$dot = strrpos($file, '.');
if ($dot === false) {
return '';
}
$ext = substr($file, $dot + 1);
// Extension cannot contain slashes.
if (strpos($ext, '/') !== false || (DIRECTORY_SEPARATOR === '\\' && strpos($ext, '\\') !== false)) {
return '';
}
return $ext;
}
/**
* Strips the last extension off of a file name
*
* @param string $file The file name
*
* @return string The file name without the extension
*
* @since 1.0
*/
public static function stripExt($file)
{
return preg_replace('#\.[^.]*$#', '', $file);
}
/**
* Makes the file name safe to use
*
* @param string $file The name of the file [not full path]
* @param array $stripChars Array of regex (by default will remove any leading periods)
*
* @return string The sanitised string
*
* @since 1.0
*/
public static function makeSafe($file, array $stripChars = ['#^\.#'])
{
// Try transliterating the file name using the native php function
if (function_exists('transliterator_transliterate') && function_exists('iconv')) {
// Using iconv to ignore characters that can't be transliterated
$file = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", transliterator_transliterate('Any-Latin; Latin-ASCII', $file));
}
$regex = array_merge(['#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#'], $stripChars);
$file = preg_replace($regex, '', $file);
// Remove any trailing dots, as those aren't ever valid file names.
$file = rtrim($file, '.');
return trim($file);
}
/**
* Copies a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
* @throws \UnexpectedValueException
*/
public static function copy($src, $dest, $path = null, $useStreams = false)
{
// Prepend a base path if it exists
if ($path) {
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src)) {
throw new \UnexpectedValueException(
sprintf(
"%s: Cannot find or read file: %s",
__METHOD__,
Path::removeRoot($src)
)
);
}
if ($useStreams) {
$stream = Stream::getStream();
if (!$stream->copy($src, $dest, null, false)) {
throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
}
self::invalidateFileCache($dest);
return true;
}
if (!@ copy($src, $dest)) {
throw new FilesystemException(__METHOD__ . ': Copy failed.');
}
self::invalidateFileCache($dest);
return true;
}
/**
* Delete a file or array of files
*
* @param mixed $file The file name or an array of file names
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function delete($file)
{
$files = (array) $file;
foreach ($files as $file) {
$file = Path::clean($file);
$filename = basename($file);
// Try making the file writable first. If it's read-only, it can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
// In case of restricted permissions we zap it one way or the other
// as long as the owner is either the webserver or the ftp
if (!@ unlink($file)) {
throw new FilesystemException(__METHOD__ . ': Failed deleting ' . $filename);
}
self::invalidateFileCache($file);
}
return true;
}
/**
* Moves a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function move($src, $dest, $path = '', $useStreams = false)
{
if ($path) {
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src)) {
return 'Cannot find source file.';
}
if ($useStreams) {
$stream = Stream::getStream();
if (!$stream->move($src, $dest, null, false)) {
throw new FilesystemException(__METHOD__ . ': ' . $stream->getError());
}
self::invalidateFileCache($dest);
return true;
}
if (!@ rename($src, $dest)) {
throw new FilesystemException(__METHOD__ . ': Rename failed.');
}
self::invalidateFileCache($dest);
return true;
}
/**
* Write contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
* @param boolean $appendToFile Append to the file and not overwrite it.
*
* @return boolean True on success
*
* @since 1.0
*/
public static function write($file, $buffer, $useStreams = false, $appendToFile = false)
{
if (\function_exists('set_time_limit')) {
set_time_limit(ini_get('max_execution_time'));
}
// If the destination directory doesn't exist we need to create it
if (!file_exists(\dirname($file))) {
Folder::create(\dirname($file));
}
if ($useStreams) {
$stream = Stream::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
$stream->writeFile($file, $buffer, $appendToFile);
self::invalidateFileCache($file);
return true;
}
$file = Path::clean($file);
// Set the required flag to only append to the file and not overwrite it
if ($appendToFile === true) {
$res = \is_int(file_put_contents($file, $buffer, \FILE_APPEND));
} else {
$res = \is_int(file_put_contents($file, $buffer));
}
self::invalidateFileCache($file);
return $res;
}
/**
* Moves an uploaded file to a destination folder
*
* @param string $src The name of the php (temporary) uploaded file
* @param string $dest The path (including filename) to move the uploaded file to
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function upload($src, $dest, $useStreams = false)
{
// Ensure that the path is valid and clean
$dest = Path::clean($dest);
// Create the destination directory if it does not exist
$baseDir = \dirname($dest);
if (!is_dir($baseDir)) {
Folder::create($baseDir);
}
if ($useStreams) {
$stream = Stream::getStream();
if (!$stream->upload($src, $dest, null, false)) {
throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
}
self::invalidateFileCache($dest);
return true;
}
if (is_writable($baseDir) && move_uploaded_file($src, $dest)) {
// Short circuit to prevent file permission errors
if (Path::setPermissions($dest)) {
self::invalidateFileCache($dest);
return true;
}
throw new FilesystemException(__METHOD__ . ': Failed to change file permissions.');
}
throw new FilesystemException(__METHOD__ . ': Failed to move file.');
}
/**
* Invalidate any opcache for a newly written file immediately, if opcache* functions exist and if this was a PHP file.
*
* @param string $file The path to the file just written to, to flush from opcache
*
* @return void
*/
public static function invalidateFileCache($file)
{
if (function_exists('opcache_invalidate')) {
$info = pathinfo($file);
if (isset($info['extension']) && $info['extension'] === 'php') {
// Force invalidation to be absolutely sure the opcache is cleared for this file.
opcache_invalidate($file, true);
}
}
}
}

View File

@ -0,0 +1,548 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
use Joomla\Filesystem\Exception\FilesystemException;
/**
* A Folder handling class
*
* @since 1.0
*/
abstract class Folder
{
/**
* Copy a folder.
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the file names.
* @param boolean $force Force copy.
* @param boolean $useStreams Optionally force folder/file overwrites.
*
* @return boolean True on success.
*
* @since 1.0
* @throws FilesystemException
*/
public static function copy($src, $dest, $path = '', $force = false, $useStreams = false)
{
if (\function_exists('set_time_limit')) {
set_time_limit(ini_get('max_execution_time'));
}
if ($path) {
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
// Eliminate trailing directory separators, if any
$src = rtrim($src, \DIRECTORY_SEPARATOR);
$dest = rtrim($dest, \DIRECTORY_SEPARATOR);
if (!is_dir(Path::clean($src))) {
throw new FilesystemException('Source folder not found', -1);
}
if (is_dir(Path::clean($dest)) && !$force) {
throw new FilesystemException('Destination folder not found', -1);
}
// Make sure the destination exists
if (!self::create($dest)) {
throw new FilesystemException('Cannot create destination folder', -1);
}
if (!($dh = @opendir($src))) {
throw new FilesystemException('Cannot open source folder', -1);
}
// Walk through the directory copying files and recursing into folders.
while (($file = readdir($dh)) !== false) {
$sfid = $src . '/' . $file;
$dfid = $dest . '/' . $file;
switch (filetype($sfid)) {
case 'dir':
if ($file != '.' && $file != '..') {
$ret = self::copy($sfid, $dfid, null, $force, $useStreams);
if ($ret !== true) {
return $ret;
}
}
break;
case 'file':
if ($useStreams) {
Stream::getStream()->copy($sfid, $dfid);
} else {
if (!@copy($sfid, $dfid)) {
throw new FilesystemException('Copy file failed', -1);
}
}
break;
}
}
return true;
}
/**
* Create a folder -- and all necessary parent folders.
*
* @param string $path A path to create from the base path.
* @param integer $mode Directory permissions to set for folders created. 0755 by default.
*
* @return boolean True if successful.
*
* @since 1.0
* @throws FilesystemException
*/
public static function create($path = '', $mode = 0755)
{
static $nested = 0;
// Check to make sure the path valid and clean
$path = Path::clean($path);
// Check if parent dir exists
$parent = \dirname($path);
if (!is_dir(Path::clean($parent))) {
// Prevent infinite loops!
$nested++;
if (($nested > 20) || ($parent == $path)) {
throw new FilesystemException(__METHOD__ . ': Infinite loop detected');
}
try {
// Create the parent directory
if (self::create($parent, $mode) !== true) {
// Folder::create throws an error
$nested--;
return false;
}
} catch (FilesystemException $exception) {
$nested--;
throw $exception;
}
// OK, parent directory has been created
$nested--;
}
// Check if dir already exists
if (is_dir(Path::clean($path))) {
return true;
}
// We need to get and explode the open_basedir paths
$obd = ini_get('open_basedir');
// If open_basedir is set we need to get the open_basedir that the path is in
if ($obd != null) {
if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
$obdSeparator = ';';
} else {
$obdSeparator = ':';
}
// Create the array of open_basedir paths
$obdArray = explode($obdSeparator, $obd);
$inBaseDir = false;
// Iterate through open_basedir paths looking for a match
foreach ($obdArray as $test) {
$test = Path::clean($test);
if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0) {
$inBaseDir = true;
break;
}
}
if ($inBaseDir == false) {
// Throw a FilesystemException because the path to be created is not in open_basedir
throw new FilesystemException(__METHOD__ . ': Path not in open_basedir paths');
}
}
// First set umask
$origmask = @umask(0);
// Create the path
if (!$ret = @mkdir($path, $mode)) {
@umask($origmask);
throw new FilesystemException(__METHOD__ . ': Could not create directory. Path: ' . $path);
}
// Reset umask
@umask($origmask);
return $ret;
}
/**
* Delete a folder.
*
* @param string $path The path to the folder to delete.
*
* @return boolean True on success.
*
* @since 1.0
* @throws FilesystemException
* @throws \UnexpectedValueException
*/
public static function delete($path)
{
if (\function_exists('set_time_limit')) {
set_time_limit(ini_get('max_execution_time'));
}
// Sanity check
if (!$path) {
// Bad programmer! Bad Bad programmer!
throw new FilesystemException(__METHOD__ . ': You can not delete a base directory.');
}
// Check to make sure the path valid and clean
$path = Path::clean($path);
// Is this really a folder?
if (!is_dir($path)) {
throw new \UnexpectedValueException(
sprintf(
'%1$s: Path is not a folder. Path: %2$s',
__METHOD__,
Path::removeRoot($path)
)
);
}
// Remove all the files in folder if they exist; disable all filtering
$files = self::files($path, '.', false, true, [], []);
if (!empty($files)) {
if (File::delete($files) !== true) {
// File::delete throws an error
return false;
}
}
// Remove sub-folders of folder; disable all filtering
$folders = self::folders($path, '.', false, true, [], []);
foreach ($folders as $folder) {
if (is_link($folder)) {
// Don't descend into linked directories, just delete the link.
if (File::delete($folder) !== true) {
// File::delete throws an error
return false;
}
} elseif (self::delete($folder) !== true) {
// Folder::delete throws an error
return false;
}
}
// In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp.
if (@rmdir($path)) {
return true;
}
throw new FilesystemException(sprintf('%1$s: Could not delete folder. Path: %2$s', __METHOD__, $path));
}
/**
* Moves a folder.
*
* @param string $src The path to the source folder.
* @param string $dest The path to the destination folder.
* @param string $path An optional base path to prefix to the file names.
* @param boolean $useStreams Optionally use streams.
*
* @return string|boolean Error message on false or boolean true on success.
*
* @since 1.0
*/
public static function move($src, $dest, $path = '', $useStreams = false)
{
if ($path) {
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
if (!is_dir(Path::clean($src))) {
return 'Cannot find source folder';
}
if (is_dir(Path::clean($dest))) {
return 'Folder already exists';
}
if ($useStreams) {
Stream::getStream()->move($src, $dest);
return true;
}
if (!@rename($src, $dest)) {
return 'Rename failed';
}
return true;
}
/**
* Utility function to read the files in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for file names.
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the file.
* @param array $exclude Array with names of files which should not be shown in the result.
* @param array $excludeFilter Array of filter to exclude
* @param boolean $naturalSort False for asort, true for natsort
*
* @return array Files in the given folder.
*
* @since 1.0
* @throws \UnexpectedValueException
*/
public static function files(
$path,
$filter = '.',
$recurse = false,
$full = false,
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
$excludeFilter = ['^\..*', '.*~'],
$naturalSort = false
) {
// Check to make sure the path valid and clean
$path = Path::clean($path);
// Is the path a folder?
if (!is_dir($path)) {
throw new \UnexpectedValueException(
sprintf(
'%1$s: Path is not a folder. Path: %2$s',
__METHOD__,
Path::removeRoot($path)
)
);
}
// Compute the excludefilter string
if (\count($excludeFilter)) {
$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
} else {
$excludeFilterString = '';
}
// Get the files
$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true);
// Sort the files based on either natural or alpha method
if ($naturalSort) {
natsort($arr);
} else {
asort($arr);
}
return array_values($arr);
}
/**
* Utility function to read the folders in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the folders.
* @param array $exclude Array with names of folders which should not be shown in the result.
* @param array $excludeFilter Array with regular expressions matching folders which should not be shown in the result.
*
* @return array Folders in the given folder.
*
* @since 1.0
* @throws \UnexpectedValueException
*/
public static function folders(
$path,
$filter = '.',
$recurse = false,
$full = false,
$exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
$excludeFilter = ['^\..*']
) {
// Check to make sure the path valid and clean
$path = Path::clean($path);
// Is the path a folder?
if (!is_dir($path)) {
throw new \UnexpectedValueException(
sprintf(
'%1$s: Path is not a folder. Path: %2$s',
__METHOD__,
Path::removeRoot($path)
)
);
}
// Compute the excludefilter string
if (\count($excludeFilter)) {
$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
} else {
$excludeFilterString = '';
}
// Get the folders
$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false);
// Sort the folders
asort($arr);
return array_values($arr);
}
/**
* Function to read the files/folders in a folder.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for file names.
* @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
* @param boolean $full True to return the full path to the file.
* @param array $exclude Array with names of files which should not be shown in the result.
* @param string $excludeFilterString Regexp of files to exclude
* @param boolean $findfiles True to read the files, false to read the folders
*
* @return array Files.
*
* @since 1.0
*/
protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findfiles)
{
if (\function_exists('set_time_limit')) {
set_time_limit(ini_get('max_execution_time'));
}
$arr = [];
// Read the source directory
if (!($handle = @opendir($path))) {
return $arr;
}
while (($file = readdir($handle)) !== false) {
if (
$file != '.' && $file != '..' && !\in_array($file, $exclude)
&& (empty($excludeFilterString) || !preg_match($excludeFilterString, $file))
) {
// Compute the fullpath
$fullpath = Path::clean($path . '/' . $file);
// Compute the isDir flag
$isDir = is_dir($fullpath);
if (($isDir xor $findfiles) && preg_match("/$filter/", $file)) {
// (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
if ($full) {
// Full path is requested
$arr[] = $fullpath;
} else {
// Filename is requested
$arr[] = $file;
}
}
if ($isDir && $recurse) {
// Search recursively
if (\is_int($recurse)) {
// Until depth 0 is reached
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findfiles));
} else {
$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findfiles));
}
}
}
}
closedir($handle);
return $arr;
}
/**
* Lists folder in format suitable for tree display.
*
* @param string $path The path of the folder to read.
* @param string $filter A filter for folder names.
* @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
* @param integer $level The current level, optional.
* @param integer $parent Unique identifier of the parent folder, if any.
*
* @return array Folders in the given folder.
*
* @since 1.0
*/
public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
{
$dirs = [];
if ($level == 0) {
$GLOBALS['_JFolder_folder_tree_index'] = 0;
}
if ($level < $maxLevel) {
$folders = self::folders($path, $filter);
// First path, index foldernames
foreach ($folders as $name) {
$id = ++$GLOBALS['_JFolder_folder_tree_index'];
$fullName = Path::clean($path . '/' . $name);
$dirs[] = [
'id' => $id,
'parent' => $parent,
'name' => $name,
'fullname' => $fullName,
'relname' => str_replace(JPATH_ROOT, '', $fullName),
];
$dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
$dirs = array_merge($dirs, $dirs2);
}
}
return $dirs;
}
/**
* Makes path name safe to use.
*
* @param string $path The full path to sanitise.
*
* @return string The sanitised string.
*
* @since 1.0
*/
public static function makeSafe($path)
{
$regex = ['#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#'];
return preg_replace($regex, '', $path);
}
}

View File

@ -0,0 +1,280 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
/**
* File system helper
*
* Holds support functions for the filesystem, particularly the stream
*
* @since 1.0
*/
class Helper
{
/**
* Remote file size function for streams that don't support it
*
* @param string $url TODO Add text
*
* @return mixed
*
* @link https://www.php.net/manual/en/function.filesize.php#71098
* @since 1.0
*/
public static function remotefsize($url)
{
$sch = parse_url($url, \PHP_URL_SCHEME);
if (!\in_array($sch, ['http', 'https', 'ftp', 'ftps'], true)) {
return false;
}
if (\in_array($sch, ['http', 'https'], true)) {
$headers = @ get_headers($url, 1);
if (!$headers || (!\array_key_exists('Content-Length', $headers))) {
return false;
}
return $headers['Content-Length'];
}
if (\in_array($sch, ['ftp', 'ftps'], true)) {
$server = parse_url($url, \PHP_URL_HOST);
$port = parse_url($url, \PHP_URL_PORT);
$path = parse_url($url, \PHP_URL_PATH);
$user = parse_url($url, \PHP_URL_USER);
$pass = parse_url($url, \PHP_URL_PASS);
if ((!$server) || (!$path)) {
return false;
}
if (!$port) {
$port = 21;
}
if (!$user) {
$user = 'anonymous';
}
if (!$pass) {
$pass = '';
}
$ftpid = null;
switch ($sch) {
case 'ftp':
$ftpid = @ftp_connect($server, $port);
break;
case 'ftps':
$ftpid = @ftp_ssl_connect($server, $port);
break;
}
if (!$ftpid) {
return false;
}
$login = @ftp_login($ftpid, $user, $pass);
if (!$login) {
return false;
}
$ftpsize = ftp_size($ftpid, $path);
ftp_close($ftpid);
if ($ftpsize == -1) {
return false;
}
return $ftpsize;
}
}
/**
* Quick FTP chmod
*
* @param string $url Link identifier
* @param integer $mode The new permissions, given as an octal value.
*
* @return integer|boolean
*
* @link https://www.php.net/manual/en/function.ftp-chmod.php
* @since 1.0
*/
public static function ftpChmod($url, $mode)
{
$sch = parse_url($url, \PHP_URL_SCHEME);
if (($sch != 'ftp') && ($sch != 'ftps')) {
return false;
}
$server = parse_url($url, \PHP_URL_HOST);
$port = parse_url($url, \PHP_URL_PORT);
$path = parse_url($url, \PHP_URL_PATH);
$user = parse_url($url, \PHP_URL_USER);
$pass = parse_url($url, \PHP_URL_PASS);
if ((!$server) || (!$path)) {
return false;
}
if (!$port) {
$port = 21;
}
if (!$user) {
$user = 'anonymous';
}
if (!$pass) {
$pass = '';
}
$ftpid = null;
switch ($sch) {
case 'ftp':
$ftpid = @ftp_connect($server, $port);
break;
case 'ftps':
$ftpid = @ftp_ssl_connect($server, $port);
break;
}
if (!$ftpid) {
return false;
}
$login = @ftp_login($ftpid, $user, $pass);
if (!$login) {
return false;
}
$res = @ftp_chmod($ftpid, $mode, $path);
ftp_close($ftpid);
return $res;
}
/**
* Modes that require a write operation
*
* @return array
*
* @since 1.0
*/
public static function getWriteModes()
{
return ['w', 'w+', 'a', 'a+', 'r+', 'x', 'x+'];
}
/**
* Stream and Filter Support Operations
*
* Returns the supported streams, in addition to direct file access
* Also includes Joomla! streams as well as PHP streams
*
* @return array Streams
*
* @since 1.0
*/
public static function getSupported()
{
// Really quite cool what php can do with arrays when you let it...
static $streams;
if (!$streams) {
$streams = array_merge(stream_get_wrappers(), self::getJStreams());
}
return $streams;
}
/**
* Returns a list of transports
*
* @return array
*
* @since 1.0
*/
public static function getTransports()
{
// Is this overkill?
return stream_get_transports();
}
/**
* Returns a list of filters
*
* @return array
*
* @since 1.0
*/
public static function getFilters()
{
// Note: This will look like the getSupported() function with J! filters.
// TODO: add user space filter loading like user space stream loading
return stream_get_filters();
}
/**
* Returns a list of J! streams
*
* @return array
*
* @since 1.0
*/
public static function getJStreams()
{
static $streams = [];
if (!$streams) {
$files = new \DirectoryIterator(__DIR__ . '/Stream');
/** @var \DirectoryIterator $file */
foreach ($files as $file) {
// Only load for php files.
if (!$file->isFile() || $file->getExtension() != 'php') {
continue;
}
$streams[] = $file->getBasename('.php');
}
}
return $streams;
}
/**
* Determine if a stream is a Joomla stream.
*
* @param string $streamname The name of a stream
*
* @return boolean True for a Joomla Stream
*
* @since 1.0
*/
public static function isJoomlaStream($streamname)
{
return \in_array($streamname, self::getJStreams());
}
}

View File

@ -0,0 +1,516 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
/**
* A Unified Diff Format Patcher class
*
* @link http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta
* @since 1.0
*/
class Patcher
{
/**
* Regular expression for searching source files
*
* @var string
* @since 1.0
*/
public const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
/**
* Regular expression for searching destination files
*
* @var string
* @since 1.0
*/
public const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
/**
* Regular expression for searching hunks of differences
*
* @var string
* @since 1.0
*/
public const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';
/**
* Regular expression for splitting lines
*
* @var string
* @since 1.0
*/
public const SPLIT = '/(\r\n)|(\r)|(\n)/';
/**
* Source files
*
* @var array
* @since 1.0
*/
protected $sources = [];
/**
* Destination files
*
* @var array
* @since 1.0
*/
protected $destinations = [];
/**
* Removal files
*
* @var array
* @since 1.0
*/
protected $removals = [];
/**
* Patches
*
* @var array
* @since 1.0
*/
protected $patches = [];
/**
* Singleton instance of this class
*
* @var Patcher
* @since 1.0
*/
protected static $instance;
/**
* Constructor
*
* The constructor is protected to force the use of Patcher::getInstance()
*
* @since 1.0
*/
protected function __construct()
{
}
/**
* Method to get a patcher
*
* @return Patcher an instance of the patcher
*
* @since 1.0
*/
public static function getInstance()
{
if (!isset(static::$instance)) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Reset the pacher
*
* @return Patcher This object for chaining
*
* @since 1.0
*/
public function reset()
{
$this->sources = [];
$this->destinations = [];
$this->removals = [];
$this->patches = [];
return $this;
}
/**
* Apply the patches
*
* @return integer The number of files patched
*
* @since 1.0
* @throws \RuntimeException
*/
public function apply()
{
foreach ($this->patches as $patch) {
// Separate the input into lines
$lines = self::splitLines($patch['udiff']);
// Loop for each header
while (self::findHeader($lines, $src, $dst)) {
$done = false;
if ($patch['strip'] === null) {
$src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src);
$dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst);
} else {
$src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src);
$dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst);
}
// Loop for each hunk of differences
while (self::findHunk($lines, $srcLine, $srcSize, $dstLine, $dstSize)) {
$done = true;
// Apply the hunk of differences
$this->applyHunk($lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize);
}
// If no modifications were found, throw an exception
if (!$done) {
throw new \RuntimeException('Invalid Diff');
}
}
}
// Initialize the counter
$done = 0;
// Patch each destination file
foreach ($this->destinations as $file => $content) {
$content = implode("\n", $content);
if (File::write($file, $content)) {
if (isset($this->sources[$file])) {
$this->sources[$file] = $content;
}
$done++;
}
}
// Remove each removed file
foreach ($this->removals as $file) {
if (File::delete($file)) {
if (isset($this->sources[$file])) {
unset($this->sources[$file]);
}
$done++;
}
}
// Clear the destinations cache
$this->destinations = [];
// Clear the removals
$this->removals = [];
// Clear the patches
$this->patches = [];
return $done;
}
/**
* Add a unified diff file to the patcher
*
* @param string $filename Path to the unified diff file
* @param string $root The files root path
* @param integer $strip The number of '/' to strip
*
* @return Patcher $this for chaining
*
* @since 1.0
*/
public function addFile($filename, $root, $strip = 0)
{
return $this->add(file_get_contents($filename), $root, $strip);
}
/**
* Add a unified diff string to the patcher
*
* @param string $udiff Unified diff input string
* @param string $root The files root path
* @param integer $strip The number of '/' to strip
*
* @return Patcher $this for chaining
*
* @since 1.0
*/
public function add($udiff, $root, $strip = 0)
{
$this->patches[] = [
'udiff' => $udiff,
'root' => isset($root) ? rtrim($root, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR : '',
'strip' => $strip,
];
return $this;
}
/**
* Separate CR or CRLF lines
*
* @param string $data Input string
*
* @return array The lines of the input destination file
*
* @since 1.0
*/
protected static function splitLines($data)
{
return preg_split(self::SPLIT, $data);
}
/**
* Find the diff header
*
* The internal array pointer of $lines is on the next line after the finding
*
* @param array $lines The udiff array of lines
* @param string $src The source file
* @param string $dst The destination file
*
* @return boolean TRUE in case of success, FALSE in case of failure
*
* @since 1.0
* @throws \RuntimeException
*/
protected static function findHeader(&$lines, &$src, &$dst)
{
// Get the current line
$line = current($lines);
// Search for the header
while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) {
$line = next($lines);
}
if ($line === false) {
// No header found, return false
return false;
}
// Set the source file
$src = $m[1];
// Advance to the next line
$line = next($lines);
if ($line === false) {
throw new \RuntimeException('Unexpected EOF');
}
// Search the destination file
if (!preg_match(self::DST_FILE, $line, $m)) {
throw new \RuntimeException('Invalid Diff file');
}
// Set the destination file
$dst = $m[1];
// Advance to the next line
if (next($lines) === false) {
throw new \RuntimeException('Unexpected EOF');
}
return true;
}
/**
* Find the next hunk of difference
*
* The internal array pointer of $lines is on the next line after the finding
*
* @param array $lines The udiff array of lines
* @param string $srcLine The beginning of the patch for the source file
* @param string $srcSize The size of the patch for the source file
* @param string $dstLine The beginning of the patch for the destination file
* @param string $dstSize The size of the patch for the destination file
*
* @return boolean TRUE in case of success, false in case of failure
*
* @since 1.0
* @throws \RuntimeException
*/
protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize)
{
$line = current($lines);
if (preg_match(self::HUNK, $line, $m)) {
$srcLine = (int) $m[1];
if ($m[3] === '') {
$srcSize = 1;
} else {
$srcSize = (int) $m[3];
}
$dstLine = (int) $m[4];
if ($m[6] === '') {
$dstSize = 1;
} else {
$dstSize = (int) $m[6];
}
if (next($lines) === false) {
throw new \RuntimeException('Unexpected EOF');
}
return true;
}
return false;
}
/**
* Apply the patch
*
* @param array $lines The udiff array of lines
* @param string $src The source file
* @param string $dst The destination file
* @param string $srcLine The beginning of the patch for the source file
* @param string $srcSize The size of the patch for the source file
* @param string $dstLine The beginning of the patch for the destination file
* @param string $dstSize The size of the patch for the destination file
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize)
{
$srcLine--;
$dstLine--;
$line = current($lines);
// Source lines (old file)
$source = [];
// New lines (new file)
$destin = [];
$srcLeft = $srcSize;
$dstLeft = $dstSize;
do {
if (!isset($line[0])) {
$source[] = '';
$destin[] = '';
$srcLeft--;
$dstLeft--;
} elseif ($line[0] == '-') {
if ($srcLeft == 0) {
throw new \RuntimeException('Unexpected remove line at line ' . key($lines));
}
$source[] = substr($line, 1);
$srcLeft--;
} elseif ($line[0] == '+') {
if ($dstLeft == 0) {
throw new \RuntimeException('Unexpected add line at line ' . key($lines));
}
$destin[] = substr($line, 1);
$dstLeft--;
} elseif ($line != '\\ No newline at end of file') {
$line = substr($line, 1);
$source[] = $line;
$destin[] = $line;
$srcLeft--;
$dstLeft--;
}
if ($srcLeft == 0 && $dstLeft == 0) {
// Now apply the patch, finally!
if ($srcSize > 0) {
$srcLines = & $this->getSource($src);
if (!isset($srcLines)) {
throw new \RuntimeException(
'Unexisting source file: ' . Path::removeRoot($src)
);
}
}
if ($dstSize > 0) {
if ($srcSize > 0) {
$dstLines = & $this->getDestination($dst, $src);
$srcBottom = $srcLine + \count($source);
for ($l = $srcLine; $l < $srcBottom; $l++) {
if ($srcLines[$l] != $source[$l - $srcLine]) {
throw new \RuntimeException(
sprintf(
'Failed source verification of file %1$s at line %2$s',
Path::removeRoot($src),
$l
)
);
}
}
array_splice($dstLines, $dstLine, \count($source), $destin);
} else {
$this->destinations[$dst] = $destin;
}
} else {
$this->removals[] = $src;
}
next($lines);
return;
}
$line = next($lines);
} while ($line !== false);
throw new \RuntimeException('Unexpected EOF');
}
/**
* Get the lines of a source file
*
* @param string $src The path of a file
*
* @return array The lines of the source file
*
* @since 1.0
*/
protected function &getSource($src)
{
if (!isset($this->sources[$src])) {
if (is_readable($src)) {
$this->sources[$src] = self::splitLines(file_get_contents($src));
} else {
$this->sources[$src] = null;
}
}
return $this->sources[$src];
}
/**
* Get the lines of a destination file
*
* @param string $dst The path of a destination file
* @param string $src The path of a source file
*
* @return array The lines of the destination file
*
* @since 1.0
*/
protected function &getDestination($dst, $src)
{
if (!isset($this->destinations[$dst])) {
$this->destinations[$dst] = $this->getSource($src);
}
return $this->destinations[$dst];
}
}

View File

@ -0,0 +1,376 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
use Joomla\Filesystem\Exception\FilesystemException;
/**
* A Path handling class
*
* @since 1.0
*/
class Path
{
/**
* Checks if a path's permissions can be changed.
*
* @param string $path Path to check.
*
* @return boolean True if path can have mode changed.
*
* @since 1.0
*/
public static function canChmod($path)
{
if (!file_exists($path)) {
return false;
}
$perms = @fileperms($path);
if ($perms !== false) {
if (@chmod($path, $perms ^ 0001)) {
@chmod($path, $perms);
return true;
}
}
return false;
}
/**
* Chmods files and directories recursively to given permissions.
*
* @param string $path Root path to begin changing mode [without trailing slash].
* @param string $filemode Octal representation of the value to change file mode to [null = no change].
* @param string $foldermode Octal representation of the value to change folder mode to [null = no change].
*
* @return boolean True if successful [one fail means the whole operation failed].
*
* @since 1.0
*/
public static function setPermissions($path, $filemode = '0644', $foldermode = '0755')
{
// Initialise return value
$ret = true;
if (is_dir($path)) {
$dh = @opendir($path);
if ($dh) {
while ($file = readdir($dh)) {
if ($file != '.' && $file != '..') {
$fullpath = $path . '/' . $file;
if (is_dir($fullpath)) {
if (!static::setPermissions($fullpath, $filemode, $foldermode)) {
$ret = false;
}
} else {
if (isset($filemode)) {
if (!static::canChmod($fullpath) || !@ chmod($fullpath, octdec($filemode))) {
$ret = false;
}
}
}
}
}
closedir($dh);
}
if (isset($foldermode)) {
if (!static::canChmod($path) || !@ chmod($path, octdec($foldermode))) {
$ret = false;
}
}
} else {
if (isset($filemode)) {
if (!static::canChmod($path) || !@ chmod($path, octdec($filemode))) {
$ret = false;
}
}
}
return $ret;
}
/**
* Get the permissions of the file/folder at a give path.
*
* @param string $path The path of a file/folder.
*
* @return string Filesystem permissions.
*
* @since 1.0
*/
public static function getPermissions($path)
{
$path = self::clean($path);
$mode = @ decoct(@ fileperms($path) & 0777);
if (\strlen($mode) < 3) {
return '---------';
}
$parsedMode = '';
for ($i = 0; $i < 3; $i++) {
// Read
$parsedMode .= ($mode[$i] & 04) ? 'r' : '-';
// Write
$parsedMode .= ($mode[$i] & 02) ? 'w' : '-';
// Execute
$parsedMode .= ($mode[$i] & 01) ? 'x' : '-';
}
return $parsedMode;
}
/**
* Checks for snooping outside of the file system root.
*
* @param string $path A file system path to check.
* @param string $basePath The base path of the system
*
* @return string A cleaned version of the path or exit on error.
*
* @since 1.0
* @throws FilesystemException
*/
public static function check($path, $basePath = '')
{
if (strpos($path, '..') !== false) {
throw new FilesystemException(
sprintf(
'%s() - Use of relative paths not permitted',
__METHOD__
),
20
);
}
$path = static::clean($path);
// If a base path is defined then check the cleaned path is not outside of root
if (($basePath != '') && strpos($path, static::clean($basePath)) !== 0) {
throw new FilesystemException(
sprintf(
'%1$s() - Snooping out of bounds @ %2$s',
__METHOD__,
$path
),
20
);
}
return $path;
}
/**
* Function to strip additional / or \ in a path name.
*
* @param string $path The path to clean.
* @param string $ds Directory separator (optional).
*
* @return string The cleaned path.
*
* @since 1.0
* @throws \UnexpectedValueException If $path is not a string.
*/
public static function clean($path, $ds = \DIRECTORY_SEPARATOR)
{
if ($path === '') {
return '';
}
if (!\is_string($path)) {
throw new \InvalidArgumentException('You must specify a non-empty path to clean');
}
$stream = explode('://', $path, 2);
$scheme = '';
$path = $stream[0];
if (\count($stream) >= 2) {
$scheme = $stream[0] . '://';
$path = $stream[1];
}
$path = trim($path);
// Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
// If dealing with a UNC path don't forget to prepend the path with a backslash.
if (($ds == '\\') && ($path[0] == '\\') && ($path[1] == '\\')) {
$path = '\\' . preg_replace('#[/\\\\]+#', $ds, $path);
} else {
$path = preg_replace('#[/\\\\]+#', $ds, $path);
}
return $scheme . $path;
}
/**
* Method to determine if script owns the path.
*
* @param string $path Path to check ownership.
*
* @return boolean True if the php script owns the path passed.
*
* @since 1.0
*/
public static function isOwner($path)
{
$tmp = md5(random_bytes(16));
$ssp = ini_get('session.save_path');
// Try to find a writable directory
$dir = is_writable('/tmp') ? '/tmp' : false;
$dir = !$dir && is_writable('.') ? '.' : $dir;
$dir = !$dir && is_writable($ssp) ? $ssp : $dir;
if ($dir) {
$test = $dir . '/' . $tmp;
// Create the test file
$blank = '';
File::write($test, $blank, false);
// Test ownership
$return = fileowner($test) === fileowner($path);
// Delete the test file
File::delete($test);
return $return;
}
return false;
}
/**
* Searches the directory paths for a given file.
*
* @param mixed $paths A path string or array of path strings to search in
* @param string $file The file name to look for.
*
* @return string|boolean The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
*
* @since 1.0
*/
public static function find($paths, $file)
{
// Force to array
if (!\is_array($paths) && !($paths instanceof \Iterator)) {
settype($paths, 'array');
}
// Start looping through the path set
foreach ($paths as $path) {
// Get the path to the file
$fullname = $path . '/' . $file;
// Is the path based on a stream?
if (strpos($path, '://') === false) {
// Not a stream, so do a realpath() to avoid directory
// traversal attempts on the local file system.
// Needed for substr() later
$path = realpath($path);
$fullname = realpath($fullname);
}
/*
* The substr() check added to make sure that the realpath()
* results in a directory registered so that
* non-registered directories are not accessible via directory
* traversal attempts.
*/
if (file_exists($fullname) && substr($fullname, 0, \strlen($path)) == $path) {
return $fullname;
}
}
// Could not find the file in the set of paths
return false;
}
/**
* Resolves /./, /../ and multiple / in a string and returns the resulting absolute path, inspired by Flysystem
* Removes trailing slashes
*
* @param string $path A path to resolve
*
* @return string The resolved path
*
* @since 1.6.0
*/
public static function resolve($path)
{
$path = static::clean($path);
// Save start character for absolute path
$startCharacter = ($path[0] === DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : '';
$parts = [];
foreach (explode(DIRECTORY_SEPARATOR, $path) as $part) {
switch ($part) {
case '':
case '.':
break;
case '..':
if (empty($parts)) {
throw new FilesystemException('Path is outside of the defined root');
}
array_pop($parts);
break;
default:
$parts[] = $part;
break;
}
}
return $startCharacter . implode(DIRECTORY_SEPARATOR, $parts);
}
/**
* Remove all references to root directory path and the system tmp path from a message
*
* @param string $message The message to be cleaned
* @param string $rootDirectory Optional root directory, defaults to JPATH_ROOT
*
* @return string
* @since 2.0.1
*/
public static function removeRoot($message, $rootDirectory = null)
{
if (empty($rootDirectory)) {
$rootDirectory = JPATH_ROOT;
}
$makePattern = static function ($dir) {
return '~' . str_replace('~', '\\~', preg_replace('~[/\\\\]+~', '.', $dir)) . '~';
};
$replacements = [
$makePattern(static::clean($rootDirectory)) => '[ROOT]',
$makePattern(sys_get_temp_dir()) => '[TMP]',
];
return preg_replace(array_keys($replacements), array_values($replacements), $message);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,290 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem\Stream;
use Joomla\Filesystem\Support\StringController;
/**
* String Stream Wrapper
*
* This class allows you to use a PHP string in the same way that you would normally use a regular stream wrapper
*
* @since 1.3.0
*/
class StringWrapper
{
/**
* The current string
*
* @var string
* @since 1.3.0
*/
protected $currentString;
/**
* The path
*
* @var string
* @since 1.3.0
*/
protected $path;
/**
* The mode
*
* @var string
* @since 1.3.0
*/
protected $mode;
/**
* Enter description here ...
*
* @var string
* @since 1.3.0
*/
protected $options;
/**
* Enter description here ...
*
* @var string
* @since 1.3.0
*/
protected $openedPath;
/**
* Current position
*
* @var integer
* @since 1.3.0
*/
protected $pos;
/**
* Length of the string
*
* @var string
* @since 1.3.0
*/
protected $len;
/**
* Statistics for a file
*
* @var array
* @since 1.3.0
* @link https://www.php.net/manual/en/function.stat.php
*/
protected $stat;
/**
* Method to open a file or URL.
*
* @param string $path The stream path.
* @param string $mode Not used.
* @param integer $options Not used.
* @param string $openedPath Not used.
*
* @return boolean
*
* @since 1.3.0
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
$refPath = StringController::getRef(str_replace('string://', '', $path));
$this->currentString = &$refPath;
if ($this->currentString) {
$this->len = \strlen($this->currentString);
$this->pos = 0;
$this->stat = $this->url_stat($path, 0);
return true;
}
return false;
}
/**
* Method to retrieve information from a file resource
*
* @return array
*
* @link https://www.php.net/manual/en/streamwrapper.stream-stat.php
* @since 1.3.0
*/
public function stream_stat()
{
return $this->stat;
}
/**
* Method to retrieve information about a file.
*
* @param string $path File path or URL to stat
* @param integer $flags Additional flags set by the streams API
*
* @return array
*
* @link https://www.php.net/manual/en/streamwrapper.url-stat.php
* @since 1.3.0
*/
public function url_stat($path, $flags = 0)
{
$now = time();
$refPath = StringController::getRef(str_replace('string://', '', $path));
$string = &$refPath;
$stat = [
'dev' => 0,
'ino' => 0,
'mode' => 0,
'nlink' => 1,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => \strlen($string),
'atime' => $now,
'mtime' => $now,
'ctime' => $now,
'blksize' => '512',
'blocks' => ceil(\strlen($string) / 512),
];
return $stat;
}
/**
* Method to read a given number of bytes starting at the current position
* and moving to the end of the string defined by the current position plus the
* given number.
*
* @param integer $count Bytes of data from the current position should be returned.
*
* @return string
*
* @link https://www.php.net/manual/en/streamwrapper.stream-read.php
* @since 1.3.0
*/
public function stream_read($count)
{
$result = substr($this->currentString, $this->pos ?? 0, $count);
$this->pos += $count;
return $result;
}
/**
* Stream write, always returning false.
*
* @param string $data The data to write.
*
* @return boolean
*
* @since 1.3.0
* @note Updating the string is not supported.
*/
public function stream_write($data)
{
// We don't support updating the string.
return false;
}
/**
* Method to get the current position
*
* @return integer The position
*
* @since 1.3.0
*/
public function stream_tell()
{
return $this->pos;
}
/**
* End of field check
*
* @return boolean True if at end of field.
*
* @since 1.3.0
*/
public function stream_eof()
{
if ($this->pos >= $this->len) {
return true;
}
return false;
}
/**
* Stream offset
*
* @param integer $offset The starting offset.
* @param integer $whence SEEK_SET, SEEK_CUR, SEEK_END
*
* @return boolean True on success.
*
* @since 1.3.0
*/
public function stream_seek($offset, $whence)
{
// $whence: SEEK_SET, SEEK_CUR, SEEK_END
if ($offset > $this->len) {
// We can't seek beyond our len.
return false;
}
switch ($whence) {
case \SEEK_SET:
$this->pos = $offset;
break;
case \SEEK_CUR:
if (($this->pos + $offset) > $this->len) {
return false;
}
$this->pos += $offset;
break;
case \SEEK_END:
$this->pos = $this->len - $offset;
break;
}
return true;
}
/**
* Stream flush, always returns true.
*
* @return boolean
*
* @since 1.3.0
* @note Data storage is not supported
*/
public function stream_flush()
{
// We don't store data.
return true;
}
}
// phpcs:disable PSR1.Files.SideEffects
if (!stream_wrapper_register('string', '\\Joomla\\Filesystem\\Stream\\StringWrapper')) {
die('\\Joomla\\Filesystem\\Stream\\StringWrapper Wrapper Registration Failed');
}
// phpcs:enable PSR1.Files.SideEffects

View File

@ -0,0 +1,84 @@
<?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem\Support;
/**
* String Controller
*
* @since 1.0
*/
class StringController
{
/**
* Internal string references
*
* @var array
* @ssince 1.4.0
*/
private static $strings = [];
/**
* Defines a variable as an array
*
* @return array
*
* @since 1.0
* @deprecated 2.0 Use `getArray` instead.
*/
public static function _getArray()
{
return self::getArray();
}
/**
* Defines a variable as an array
*
* @return array
*
* @since 1.4.0
*/
public static function getArray()
{
return self::$strings;
}
/**
* Create a reference
*
* @param string $reference The key
* @param string $string The value
*
* @return void
*
* @since 1.0
*/
public static function createRef($reference, &$string)
{
self::$strings[$reference] = & $string;
}
/**
* Get reference
*
* @param string $reference The key for the reference.
*
* @return mixed False if not set, reference if it exists
*
* @since 1.0
*/
public static function getRef($reference)
{
if (isset(self::$strings[$reference])) {
return self::$strings[$reference];
}
return false;
}
}