Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Util/File.php
<?php

namespace XF\Util;

use function
array_slice, count, strlen, strval;

class
File
{
    protected static
$tempFiles = [];
    protected static
$tempCleanUpRegistered = false;

    protected static
$tempDirs = [];
    protected static
$tempDirCleanUpRegistered = false;

    protected static
$defaultFilePermissions = null;

    public static function
getCodeCachePath()
    {
       
$config = \XF::app()->config();

        return
self::canonicalizePath(
           
sprintf($config['codeCachePath'], $config['internalDataPath'])
        );
    }

    public static function
getTempDir()
    {
       
$config = \XF::app()->config();
       
$path = self::canonicalizePath(
           
sprintf($config['tempDataPath'], $config['internalDataPath'])
        );
        if (!
is_dir($path))
        {
           
self::createDirectory($path);
        }

        return
$path;
    }

    public static function
getTempFile($autoCleanUp = true)
    {
       
$dir = self::getTempDir();
       
$file = tempnam($dir, 'xf');

        if (
$autoCleanUp)
        {
           
self::registerTempFile($file);
        }

        return
$file;
    }

    public static function
getNamedTempFile($fileName, $autoCleanUp = true)
    {
       
$file = self::getTempDir() . \XF::$DS . $fileName;

        if (
$autoCleanUp)
        {
           
self::registerTempFile($file);
        }

        return
$file;
    }

    public static function
createTempDir($autoCleanUp = true)
    {
       
$dir = self::getTempDir() . \XF::$DS . 'xf' . substr(md5(uniqid()), 0, 6);

       
self::createDirectory($dir, false);
        if (
$autoCleanUp)
        {
           
self::registerTempDirectory($dir);
        }

        return
$dir;
    }

    protected static function
registerTempFile($file)
    {
       
self::$tempFiles[] = $file;

        if (!
self::$tempCleanUpRegistered)
        {
           
register_shutdown_function([__CLASS__, 'cleanUpTempFiles']);
           
self::$tempCleanUpRegistered = true;
        }
    }

    protected static function
registerTempDirectory($dir)
    {
       
self::$tempDirs[] = $dir;

        if (!
self::$tempDirCleanUpRegistered)
        {
           
register_shutdown_function([__CLASS__, 'cleanUpTempDirs']);
           
self::$tempDirCleanUpRegistered = true;
        }
    }

    public static function
cleanUpTempFiles()
    {
        foreach (
self::$tempFiles AS $file)
        {
            try
            {
                @
unlink($file);
            }
            catch (\
Exception $e) {}
        }

       
self::$tempFiles = [];
    }

    public static function
cleanUpTempDirs()
    {
        foreach (
self::$tempDirs AS $dir)
        {
            try
            {
               
self::deleteDirectory($dir);
            }
            catch (\
Exception $e) {}
        }

       
self::$tempDirs = [];
    }

    public static function
cleanUpPersistentTempFiles($cutOff = null)
    {
       
$tempDir = self::getTempDir();
        if (!
file_exists($tempDir) || !is_dir($tempDir))
        {
            return;
        }

        if (
$cutOff === null)
        {
           
$cutOff = \XF::$time - 3 * 86400;
        }

       
$timer = new \XF\Timer(3);

       
$dir = new \DirectoryIterator($tempDir);
        foreach (
$dir AS $file)
        {
            if (!
$file->isFile() || $file->getFilename() == 'index.html')
            {
                continue;
            }

           
$mTime = $file->getMTime();
            if (
$mTime && $mTime < $cutOff)
            {
                @
unlink($file->getPathname());
            }

            if (
$timer->limitExceeded())
            {
                break;
            }
        }
    }

    public static function
copyStreamToTempFile($sourceResource, $autoCleanUp = true)
    {
       
$tempFile = self::getTempFile($autoCleanUp);

       
$tempResource = fopen($tempFile, 'w');
       
stream_copy_to_stream($sourceResource, $tempResource);
       
fclose($tempResource);

        return
$tempFile;
    }

    public static function
copyAbstractedPathToTempFile($abstractedPath, $autoCleanUp = true)
    {
       
$stream = \XF::app()->fs()->readStream($abstractedPath);
       
$tempFile = self::copyStreamToTempFile($stream, $autoCleanUp);
        @
fclose($stream); // some streams may close after reading

       
return $tempFile;
    }

    public static function
copyFileToAbstractedPath($file, $abstractedPath, array $config = [])
    {
       
$fp = fopen($file, 'r');
       
$result = \XF::app()->fs()->putStream($abstractedPath, $fp, $config);
        @
fclose($fp); // some streams may close after reading

       
return $result;
    }

    public static function
writeToAbstractedPath($abstractedPath, $contents, array $config = [], $allowOverwrite = true)
    {
       
$fs = \XF::app()->fs();

        if (
$allowOverwrite)
        {
            return
$fs->put($abstractedPath, $contents, $config);
        }
        else
        {
            return
$fs->write($abstractedPath, $contents, $config);
        }
    }

    public static function
deleteFromAbstractedPath($abstractedPath)
    {
        try
        {
            \
XF::app()->fs()->delete($abstractedPath);
        }
        catch (\
League\Flysystem\FileNotFoundException $e) {}
    }

    public static function
deleteAbstractedDirectory($abstractedDir)
    {
        try
        {
            \
XF::app()->fs()->deleteDir($abstractedDir);
        }
        catch (\
League\Flysystem\FileNotFoundException $e) {}
    }

    public static function
abstractedPathExists($abstractedPath)
    {
        return \
XF::app()->fs()->has($abstractedPath);
    }

    public static function
createDirectory($path, $createIndexHtml = true)
    {
        if (
file_exists($path))
        {
            if (
is_dir($path))
            {
                return
true;
            }

            throw new \
LogicException("Attempting to created directory $path but a non-directory already exists at $path");
        }

       
$path = str_replace('\\', '/', $path);
       
$path = rtrim($path, '/');
       
$parts = explode('/', $path);
       
$pathPartCount = count($parts);
       
$partialPath = '';

       
$rootDir = str_replace('\\', '/', \XF::getRootDirectory());

       
// find the "lowest" part that exists (and is a dir)...
       
for ($i = $pathPartCount - 1; $i >= 0; $i--)
        {
           
$partialPath = implode('/', array_slice($parts, 0, $i + 1));

            if (
file_exists($partialPath))
            {
                if (!
is_dir($partialPath))
                {
                    throw new \
LogicException("Attempting to created directory $path but a non-directory already exists at $partialPath");
                }
                else
                {
                    break;
                }
            }

            if (
$partialPath == $rootDir)
            {
                return
false; // can't go above the root dir
           
}
        }
        if (
$i < 0)
        {
            return
false;
        }

       
$i++; // skip over the last entry (as it exists)

        // ... now create directories for anything below it
       
for (; $i < $pathPartCount; $i++)
        {
           
$partialPath .= '/' . $parts[$i];
            if (!
mkdir($partialPath))
            {
                return
false;
            }

           
self::makeWritableByFtpUser($partialPath);

            if (
$createIndexHtml)
            {
                if (@
file_put_contents($partialPath . '/index.html', ' '))
                {
                   
self::makeWritableByFtpUser($partialPath . '/index.html');
                }
            }
        }

        return
true;
    }

    public static function
writeFile($file, $data, $createIndexHtml = true)
    {
       
$dir = dirname($file);
        if (
self::createDirectory($dir, $createIndexHtml))
        {
            if (
file_put_contents($file, $data) !== false)
            {
               
self::makeWritableByFtpUser($file);
                return
true;
            }
            else
            {
                return
false;
            }
        }
        else
        {
            return
false;
        }
    }

    public static function
copyFile($source, $destination, $createIndexHtml = true)
    {
       
$dir = dirname($destination);
        if (
self::createDirectory($dir, $createIndexHtml))
        {
            if (
copy($source, $destination))
            {
               
self::makeWritableByFtpUser($destination);
                return
true;
            }
            else
            {
                return
false;
            }
        }
        else
        {
            return
false;
        }
    }

   
/**
     * Given a source and a destination, this function will iterate through the source directory, copying files and directories to destination.
     * This function operates recursively.
     * Generally shouldn't need to create index HTML files with this. But you may need to invalidate opcache.
     *
     * @param $source
     * @param $destination
     * @param bool $createIndexHtml
     * @param bool $opcacheInvalidate
     *
     * @return bool
     */
   
public static function copyDirectory($source, $destination, $opcacheInvalidate = true, $createIndexHtml = false)
    {
        if (!
is_dir($destination))
        {
            if (!
self::createDirectory($destination, $createIndexHtml))
            {
                return
false;
            }
        }

        foreach (new \
DirectoryIterator($source) AS $file)
        {
           
$newDestination = $destination . \XF::$DS . $file->getFilename();

            if (!
$file->isDot() && $file->isDir())
            {
               
self::copyDirectory(
                   
$file->getRealPath(),
                   
$newDestination,
                   
$createIndexHtml,
                   
$opcacheInvalidate
               
);
            }
            else if (
$file->isFile())
            {
               
self::copyFile(
                   
$file->getRealPath(),
                   
$newDestination,
                   
$createIndexHtml
               
);

                if (
$opcacheInvalidate)
                {
                    \
XF\Util\Php::invalidateOpcodeCache($newDestination);
                }
            }
        }

        return
true;
    }

   
/**
     * Performs a cross-stream/device safe rename by falling back
     * to a copy+delete if needed.
     *
     * @param string $source
     * @param string $destination
     * @param bool $createIndexHtml
     *
     * @return bool
     */
   
public static function renameFile($source, $destination, $createIndexHtml = true)
    {
       
$dir = dirname($destination);
        if (!
self::createDirectory($dir, $createIndexHtml))
        {
            return
false;
        }

        try
        {
           
$success = rename($source, $destination);
        }
        catch (\
Exception $e)
        {
           
// possibly a perm problem, but may be an issue moving across streams, so copy+delete instead
           
$success = false;
        }

        if (!
$success)
        {
           
$success = copy($source, $destination);
            if (
$success)
            {
                @
unlink($source);
            }
        }

        if (
$success)
        {
           
self::makeWritableByFtpUser($destination);
        }

        return
$success;
    }

    public static function
getDefaultFilePermissions()
    {
        if (
self::$defaultFilePermissions)
        {
            return
self::$defaultFilePermissions;
        }

       
$chmod = \XF::app()->config('chmodWritableValue');
        if (!
$chmod)
        {
           
$selfWritable = null;

            if (
PHP_SAPI == 'cli' && function_exists('posix_getuid'))
            {
               
$lock = self::canonicalizePath(\XF::app()->config('internalDataPath') . '/install-lock.php');
               
$uid = @posix_getuid();
                if (
$uid == 0)
                {
                   
// if we're root, just force w+w
                   
$selfWritable = false;
                }
                else if (
file_exists($lock))
                {
                   
// if we're running as who created the lock file,
                    // then the web access is likely running with the same user
                   
$selfWritable = ($uid == fileowner($lock));
                }
            }

            if (
$selfWritable === null)
            {
               
$selfWritable = @is_writable(__FILE__);
            }

            if (
$selfWritable)
            {
               
// writable - probably owned by ftp user already
               
$chmod = 0644;
            }
            else
            {
               
// not writable, so file is probably owned by "nobody", need to w+w it
               
$chmod = 0666;
            }
        }

       
self::$defaultFilePermissions = [
           
'file' => $chmod,
           
'dir' => $chmod | 0111
       
];

        return
self::$defaultFilePermissions;
    }

    protected static
$dirWritableCache = [];

   
/**
     * Determines if the named file is writable if it exists or if it can likely be created if it doesn't.
     * Writable directory results will be cached and can be cleared via clearDirWritableCache().
     *
     * @param string $file
     *
     * @return bool
     */
   
public static function isWritable($file)
    {
       
$file = rtrim(str_replace('\\', '/', $file), '/');

        if (
file_exists($file))
        {
            return
is_writable($file);
        }

       
$rootDir = rtrim(str_replace('\\', '/', \XF::getRootDirectory()), '/');

       
$dir = dirname($file);
       
$originalDir = $dir;

        if (isset(
self::$dirWritableCache[$dir]))
        {
            return
self::$dirWritableCache[$dir];
        }

        while (
$dir)
        {
            if (isset(
self::$dirWritableCache[$dir]))
            {
               
$result = self::$dirWritableCache[$dir];

                if (!isset(
self::$dirWritableCache[$originalDir]))
                {
                   
self::$dirWritableCache[$originalDir] = $result;
                }

                return
$result;
            }

            if (
file_exists($dir))
            {
               
$result = (is_dir($dir) && is_writable($dir));

                if (!isset(
self::$dirWritableCache[$dir]))
                {
                   
self::$dirWritableCache[$dir] = $result;
                }
               
self::$dirWritableCache[$originalDir] = $result;

                return
$result;
            }

            if (
$dir === $rootDir)
            {
               
// can't go up a level
               
return false;
            }

           
$dir = dirname($dir);
        }

        return
false;
    }

   
/**
     * Clears the writable directory cache values used by isWritable().
     */
   
public static function clearDirWritableCache()
    {
       
self::$dirWritableCache = [];
    }

   
/**
     * Ensures that the specified file can be written to by the FTP user.
     * This generally doesn't need to do anything if PHP is running via
     * some form of suexec. It's primarily needed when running as apache.
     *
     * @param string $file
     * @return boolean
     */
   
public static function makeWritableByFtpUser($file)
    {
        if (@
is_readable($file))
        {
           
$permissions = self::getDefaultFilePermissions();
           
$chmod = @is_file($file) ? $permissions['file'] : $permissions['dir'];
            return @
chmod($file, $chmod);
        }
        else
        {
            return
false;
        }
    }

    public static function
deleteDirectory($path)
    {
       
$path = self::canonicalizePath($path);
        if (!
is_readable($path) || !is_dir($path))
        {
            throw new \
InvalidArgumentException("$path is not readable or not a directory");
        }

        if (!
is_writable($path))
        {
            throw new \
InvalidArgumentException("$path is not writable");
        }

       
$files = self::getRecursiveDirectoryIterator($path);
        foreach (
$files AS $file)
        {
            if (
$file->isLink())
            {
               
unlink($file->getPath() . \XF::$DS . $file->getFilename());
            }
            else if (
$file->isDir())
            {
               
rmdir($file->getRealPath());
            }
            else
            {
               
unlink($file->getRealPath());
            }
        }

       
rmdir($path);
    }

    public static function
canonicalizePath($path, $root = null)
    {
        if (
preg_match('#^(/|\\\\|[a-z0-9]+:)#i', $path))
        {
            return
$path; // absolute already
       
}

        if (
$root === null)
        {
           
$root = \XF::getRootDirectory();
        }

        if (\
XF::$DS !== '/')
        {
           
$path = str_replace('/', \XF::$DS, $path);
        }

        return
$root . \XF::$DS . $path;
    }

    public static function
stripRootPathPrefix($path, $root = null)
    {
        if (
$root === null)
        {
           
$root = \XF::getRootDirectory();
        }
       
$root = rtrim($root, '/\\') . \XF::$DS;

        if (\
XF::$DS !== '/')
        {
           
$root = str_replace('/', \XF::$DS, $root);
        }

       
$realPath = realpath($path);
        if (
$realPath)
        {
           
$path = realpath($path);
        }

        if (
strpos($path, $root) === 0)
        {
            return
strval(substr($path, strlen($root)));
        }
        else
        {
            return
$path;
        }
    }

   
/**
     * @param $dir
     * @param null $dirIteratorFlags
     * @param null $iteratorMode
     * @return \RecursiveIteratorIterator|\SplFileInfo[]
     */
   
public static function getRecursiveDirectoryIterator($dir, $iteratorMode = \RecursiveIteratorIterator::CHILD_FIRST, $dirIteratorFlags = \RecursiveDirectoryIterator::SKIP_DOTS)
    {
        if (
$dirIteratorFlags === null)
        {
           
$dirIterator = new \RecursiveDirectoryIterator($dir);
        }
        else
        {
           
$dirIterator = new \RecursiveDirectoryIterator($dir, $dirIteratorFlags);
        }

        if (
$iteratorMode === null)
        {
            return new \
RecursiveIteratorIterator($dirIterator);
        }
        else
        {
            return new \
RecursiveIteratorIterator($dirIterator, $iteratorMode);
        }
    }

   
/**
     * Method for writing out a file log.
     *
     * @param string $logName 'foo' will write to {tempDataPath}/foo.log
     * @param string $logEntry The string to write into the log. Line break not required.
     * @param boolean $append Append the log entry to the end of the existing log. Otherwise, start again.
     *
     * @return boolean True on successful log write
     */
   
public static function log($logName, $logEntry, $append = true)
    {
       
$logName = preg_replace('/[^a-z0-9._-]/i', '', $logName);
       
$path = self::getTempDir();
       
$file = "$path/$logName.log";

        if (
$fp = @fopen($file, ($append ? 'a' : 'w')))
        {
           
fwrite($fp, date('Y-m-d H:i:s') . ' ' . $logEntry . "\n");
           
fclose($fp);

            return
true;
        }

        return
false;
    }

    public static function
writeInstallLock()
    {
       
$contents = '<?php header(\'Location: ../index.php\'); /* Installed: ' . date(DATE_RFC822) . ' */';
        \
XF::fs()->write('internal-data://install-lock.php', $contents);
    }

    public static function
installLockExists()
    {
        try
        {
           
// If this path doesn't exist, then this will throw an exception. We need to handle this elsewhere.
           
return \XF::fs()->has('internal-data://install-lock.php');
        }
        catch (\
Exception $e)
        {
            return
false;
        }
    }

   
/**
     * Gets a file's extension in lower case. This only includes the last
     * extension (eg, x.tar.gz -> gz).
     *
     * @param string $filename
     *
     * @return string
     */
   
public static function getFileExtension($filename)
    {
        return
strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    }

    public static function
getImageMimeType(string $filename): string
   
{
       
$types = \XF::app()->inlineImageTypes;
       
$extension = self::getFileExtension($filename);

        return
$types[$extension] ?? '';
    }

    public static function
isImageInlineDisplaySafe($extension, &$contentType = null)
    {
        return
self::isFileInlineDisplaySafe(
            \
XF::app()->inlineImageTypes,
           
$extension,
           
$contentType
       
);
    }

    public static function
isVideoInlineDisplaySafe($extension, &$contentType = null)
    {
        return
self::isFileInlineDisplaySafe(
            \
XF::app()->inlineVideoTypes,
           
$extension,
           
$contentType
       
);
    }

    public static function
isAudioInlineDisplaySafe($extension, &$contentType = null)
    {
        return
self::isFileInlineDisplaySafe(
            \
XF::app()->inlineAudioTypes,
           
$extension,
           
$contentType
       
);
    }

    public static function
isFileInlineDisplaySafe(array $types, string $extension, &$contentType = null)
    {
        if (!
$extension)
        {
            return
false;
        }

       
$extension = strtolower($extension);

        if (!isset(
$types[$extension]))
        {
            return
false;
        }

       
$contentType = $types[$extension];

        return
true;
    }
}