diff options
Diffstat (limited to 'lib/private/legacy/OC_Helper.php')
-rw-r--r-- | lib/private/legacy/OC_Helper.php | 472 |
1 files changed, 121 insertions, 351 deletions
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index 0d1903007c2..4388f775623 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -1,197 +1,69 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Ardinis <Ardinis@users.noreply.github.com> - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bart Visscher <bartv@thisnet.nl> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Felix Moeller <mail@felixmoeller.de> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Jakob Sack <mail@jakobsack.de> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Olivier Paroz <github@oparoz.com> - * @author Pellaeon Lin <nfsmwlin@gmail.com> - * @author RealRancor <fisch.666@gmx.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Simon Könnecke <simonkoennecke@gmail.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ use bantu\IniGetWrapper\IniGetWrapper; +use OC\Files\FilenameValidator; use OC\Files\Filesystem; use OCP\Files\Mount\IMountPoint; +use OCP\IBinaryFinder; use OCP\ICacheFactory; use OCP\IUser; +use OCP\Server; +use OCP\Util; use Psr\Log\LoggerInterface; -use Symfony\Component\Process\ExecutableFinder; /** * Collection of useful functions + * + * @psalm-type StorageInfo = array{ + * free: float|int, + * mountPoint: string, + * mountType: string, + * owner: string, + * ownerDisplayName: string, + * quota: float|int, + * relative: float|int, + * total: float|int, + * used: float|int, + * } */ class OC_Helper { private static $templateManager; - - /** - * Make a human file size - * @param int $bytes file size in bytes - * @return string a human readable file size - * - * Makes 2048 to 2 kB. - */ - public static function humanFileSize($bytes) { - if ($bytes < 0) { - return "?"; - } - if ($bytes < 1024) { - return "$bytes B"; - } - $bytes = round($bytes / 1024, 0); - if ($bytes < 1024) { - return "$bytes KB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes MB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes GB"; - } - $bytes = round($bytes / 1024, 1); - if ($bytes < 1024) { - return "$bytes TB"; - } - - $bytes = round($bytes / 1024, 1); - return "$bytes PB"; - } - - /** - * Make a computer file size - * @param string $str file size in human readable format - * @return float|bool a file size in bytes - * - * Makes 2kB to 2048. - * - * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418 - */ - public static function computerFileSize($str) { - $str = strtolower($str); - if (is_numeric($str)) { - return (float)$str; - } - - $bytes_array = [ - 'b' => 1, - 'k' => 1024, - 'kb' => 1024, - 'mb' => 1024 * 1024, - 'm' => 1024 * 1024, - 'gb' => 1024 * 1024 * 1024, - 'g' => 1024 * 1024 * 1024, - 'tb' => 1024 * 1024 * 1024 * 1024, - 't' => 1024 * 1024 * 1024 * 1024, - 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, - 'p' => 1024 * 1024 * 1024 * 1024 * 1024, - ]; - - $bytes = (float)$str; - - if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) { - $bytes *= $bytes_array[$matches[1]]; - } else { - return false; - } - - $bytes = round($bytes); - - return $bytes; - } + private static ?ICacheFactory $cacheFactory = null; + private static ?bool $quotaIncludeExternalStorage = null; /** * Recursive copying of folders * @param string $src source folder * @param string $dest target folder - * + * @return void + * @deprecated 32.0.0 - use \OCP\Files\Folder::copy */ public static function copyr($src, $dest) { + if (!file_exists($src)) { + return; + } + if (is_dir($src)) { if (!is_dir($dest)) { mkdir($dest); } $files = scandir($src); foreach ($files as $file) { - if ($file != "." && $file != "..") { + if ($file != '.' && $file != '..') { self::copyr("$src/$file", "$dest/$file"); } } - } elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) { - copy($src, $dest); - } - } - - /** - * Recursive deletion of folders - * @param string $dir path to the folder - * @param bool $deleteSelf if set to false only the content of the folder will be deleted - * @return bool - */ - public static function rmdirr($dir, $deleteSelf = true) { - if (is_dir($dir)) { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($files as $fileInfo) { - /** @var SplFileInfo $fileInfo */ - if ($fileInfo->isLink()) { - unlink($fileInfo->getPathname()); - } elseif ($fileInfo->isDir()) { - rmdir($fileInfo->getRealPath()); - } else { - unlink($fileInfo->getRealPath()); - } - } - if ($deleteSelf) { - rmdir($dir); - } - } elseif (file_exists($dir)) { - if ($deleteSelf) { - unlink($dir); + } else { + $validator = \OCP\Server::get(FilenameValidator::class); + if (!$validator->isForbidden($src)) { + copy($src, $dest); } } - if (!$deleteSelf) { - return true; - } - - return !file_exists($dir); } /** @@ -212,21 +84,22 @@ class OC_Helper { * @param bool $path * @internal param string $program name * @internal param string $optional search path, defaults to $PATH - * @return bool true if executable program found in path + * @return bool true if executable program found in path + * @deprecated 32.0.0 use the \OCP\IBinaryFinder */ public static function canExecute($name, $path = false) { // path defaults to PATH from environment if not set if ($path === false) { - $path = getenv("PATH"); + $path = getenv('PATH'); } // we look for an executable file of that name - $exts = [""]; - $check_fn = "is_executable"; + $exts = ['']; + $check_fn = 'is_executable'; // Default check will be done with $path directories : - $dirs = explode(PATH_SEPARATOR, $path); + $dirs = explode(PATH_SEPARATOR, (string)$path); // WARNING : We have to check if open_basedir is enabled : $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir'); - if ($obd != "none") { + if ($obd != 'none') { $obd_values = explode(PATH_SEPARATOR, $obd); if (count($obd_values) > 0 and $obd_values[0]) { // open_basedir is in effect ! @@ -250,31 +123,10 @@ class OC_Helper { * @param resource $source * @param resource $target * @return array the number of bytes copied and result + * @deprecated 5.0.0 - Use \OCP\Files::streamCopy */ public static function streamCopy($source, $target) { - if (!$source or !$target) { - return [0, false]; - } - $bufSize = 8192; - $result = true; - $count = 0; - while (!feof($source)) { - $buf = fread($source, $bufSize); - $bytesWritten = fwrite($target, $buf); - if ($bytesWritten !== false) { - $count += $bytesWritten; - } - // note: strlen is expensive so only use it when necessary, - // on the last block - if ($bytesWritten === false - || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) - ) { - // write error, could be disk full ? - $result = false; - break; - } - } - return [$count, $result]; + return \OCP\Files::streamCopy($source, $target, true); } /** @@ -337,144 +189,21 @@ class OC_Helper { } /** - * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. - * - * @param array $input The array to work on - * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) - * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 - * @return array - * - * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. - * based on https://www.php.net/manual/en/function.array-change-key-case.php#107715 - * - */ - public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { - $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; - $ret = []; - foreach ($input as $k => $v) { - $ret[mb_convert_case($k, $case, $encoding)] = $v; - } - return $ret; - } - - /** - * performs a search in a nested array - * @param array $haystack the array to be searched - * @param string $needle the search string - * @param mixed $index optional, only search this key name - * @return mixed the key of the matching field, otherwise false - * - * performs a search in a nested array - * - * taken from https://www.php.net/manual/en/function.array-search.php#97645 - */ - public static function recursiveArraySearch($haystack, $needle, $index = null) { - $aIt = new RecursiveArrayIterator($haystack); - $it = new RecursiveIteratorIterator($aIt); - - while ($it->valid()) { - if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) { - return $aIt->key(); - } - - $it->next(); - } - - return false; - } - - /** - * calculates the maximum upload size respecting system settings, free space and user quota - * - * @param string $dir the current folder where the user currently operates - * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly - * @return int number of bytes representing - */ - public static function maxUploadFilesize($dir, $freeSpace = null) { - if (is_null($freeSpace) || $freeSpace < 0) { - $freeSpace = self::freeSpace($dir); - } - return min($freeSpace, self::uploadLimit()); - } - - /** - * Calculate free space left within user quota - * - * @param string $dir the current folder where the user currently operates - * @return int number of bytes representing - */ - public static function freeSpace($dir) { - $freeSpace = \OC\Files\Filesystem::free_space($dir); - if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { - $freeSpace = max($freeSpace, 0); - return $freeSpace; - } else { - return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 - } - } - - /** - * Calculate PHP upload limit - * - * @return int PHP upload file size limit - */ - public static function uploadLimit() { - $ini = \OC::$server->get(IniGetWrapper::class); - $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize')); - $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size')); - if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { - return INF; - } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) { - return max($upload_max_filesize, $post_max_size); //only the non 0 value counts - } else { - return min($upload_max_filesize, $post_max_size); - } - } - - /** * Checks if a function is available * - * @param string $function_name - * @return bool + * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead */ - public static function is_function_enabled($function_name) { - if (!function_exists($function_name)) { - return false; - } - $ini = \OC::$server->get(IniGetWrapper::class); - $disabled = explode(',', $ini->get('disable_functions') ?: ''); - $disabled = array_map('trim', $disabled); - if (in_array($function_name, $disabled)) { - return false; - } - $disabled = explode(',', $ini->get('suhosin.executor.func.blacklist') ?: ''); - $disabled = array_map('trim', $disabled); - if (in_array($function_name, $disabled)) { - return false; - } - return true; + public static function is_function_enabled(string $function_name): bool { + return Util::isFunctionEnabled($function_name); } /** * Try to find a program - * - * @param string $program - * @return null|string + * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly */ - public static function findBinaryPath($program) { - $memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath'); - if ($memcache->hasKey($program)) { - return $memcache->get($program); - } - $result = null; - if (self::is_function_enabled('exec')) { - $exeSniffer = new ExecutableFinder(); - // Returns null if nothing is found - $result = $exeSniffer->find($program, null, ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/opt/bin']); - } - // store the value for 5 minutes - $memcache->set($program, $result, 300); - return $result; + public static function findBinaryPath(string $program): ?string { + $result = Server::get(IBinaryFinder::class)->findBinaryPath($program); + return $result !== false ? $result : null; } /** @@ -485,45 +214,56 @@ class OC_Helper { * * @param string $path * @param \OCP\Files\FileInfo $rootInfo (optional) - * @return array + * @param bool $includeMountPoints whether to include mount points in the size calculation + * @param bool $useCache whether to use the cached quota values + * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct + * @return StorageInfo * @throws \OCP\Files\NotFoundException */ - public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true) { - /** @var ICacheFactory $cacheFactory */ - $cacheFactory = \OC::$server->get(ICacheFactory::class); - $memcache = $cacheFactory->createLocal('storage_info'); + public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) { + if (!self::$cacheFactory) { + self::$cacheFactory = Server::get(ICacheFactory::class); + } + $memcache = self::$cacheFactory->createLocal('storage_info'); // return storage info without adding mount points - $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); + if (self::$quotaIncludeExternalStorage === null) { + self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); + } - $fullPath = Filesystem::getView()->getAbsolutePath($path); - $cacheKey = $fullPath. '::' . ($includeMountPoints ? 'include' : 'exclude'); - $cached = $memcache->get($cacheKey); - if ($cached) { - return $cached; + $view = Filesystem::getView(); + if (!$view) { + throw new \OCP\Files\NotFoundException(); + } + $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path)); + + $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude'); + if ($useCache) { + $cached = $memcache->get($cacheKey); + if ($cached) { + return $cached; + } } if (!$rootInfo) { - $rootInfo = \OC\Files\Filesystem::getFileInfo($path, $includeExtStorage ? 'ext' : false); + $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false); } if (!$rootInfo instanceof \OCP\Files\FileInfo) { - throw new \OCP\Files\NotFoundException(); + throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing'); } $used = $rootInfo->getSize($includeMountPoints); if ($used < 0) { - $used = 0; + $used = 0.0; } + /** @var int|float $quota */ $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; $mount = $rootInfo->getMountPoint(); $storage = $mount->getStorage(); $sourceStorage = $storage; if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { - $includeExtStorage = false; - $internalPath = $storage->getUnjailedPath($rootInfo->getInternalPath()); - } else { - $internalPath = $rootInfo->getInternalPath(); + self::$quotaIncludeExternalStorage = false; } - if ($includeExtStorage) { + if (self::$quotaIncludeExternalStorage) { if ($storage->instanceOfStorage('\OC\Files\Storage\Home') || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') ) { @@ -532,7 +272,7 @@ class OC_Helper { } else { $user = \OC::$server->getUserSession()->getUser(); } - $quota = OC_Util::getUserQuota($user); + $quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN; if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { // always get free space / total space from root + mount points return self::getGlobalStorageInfo($quota, $user, $mount); @@ -545,15 +285,18 @@ class OC_Helper { $quota = $sourceStorage->getQuota(); } try { - $free = $sourceStorage->free_space($internalPath); + $free = $sourceStorage->free_space($rootInfo->getInternalPath()); + if (is_bool($free)) { + $free = 0.0; + } } catch (\Exception $e) { - if ($path === "") { + if ($path === '') { throw $e; } /** @var LoggerInterface $logger */ $logger = \OC::$server->get(LoggerInterface::class); - $logger->warning("Error while getting quota info, using root quota", ['exception' => $e]); - $rootInfo = self::getStorageInfo(""); + $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]); + $rootInfo = self::getStorageInfo(''); $memcache->set($cacheKey, $rootInfo, 5 * 60); return $rootInfo; } @@ -572,12 +315,19 @@ class OC_Helper { $relative = 0; } + /* + * \OCA\Files_Sharing\External\Storage returns the cloud ID as the owner for the storage. + * It is unnecessary to query the user manager for the display name, as it won't have this information. + */ + $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class); + $ownerId = $storage->getOwner($path); $ownerDisplayName = ''; - $owner = \OC::$server->getUserManager()->get($ownerId); - if ($owner) { - $ownerDisplayName = $owner->getDisplayName(); + + if ($isRemoteShare === false && $ownerId !== false) { + $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? ''; } + if (substr_count($mount->getMountPoint(), '/') < 3) { $mountPoint = ''; } else { @@ -596,6 +346,11 @@ class OC_Helper { 'mountPoint' => trim($mountPoint, '/'), ]; + if ($isRemoteShare === false && $ownerId !== false && $path === '/') { + // If path is root, store this as last known quota usage for this user + \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative); + } + $memcache->set($cacheKey, $info, 5 * 60); return $info; @@ -603,15 +358,20 @@ class OC_Helper { /** * Get storage info including all mount points and quota + * + * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct + * @return StorageInfo */ - private static function getGlobalStorageInfo(int $quota, IUser $user, IMountPoint $mount): array { + private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array { $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); + /** @var int|float $used */ $used = $rootInfo['size']; if ($used < 0) { - $used = 0; + $used = 0.0; } $total = $quota; + /** @var int|float $free */ $free = $quota - $used; if ($total > 0) { @@ -621,7 +381,7 @@ class OC_Helper { // prevent division by zero or error codes (negative values) $relative = round(($used / $total) * 10000) / 100; } else { - $relative = 0; + $relative = 0.0; } if (substr_count($mount->getMountPoint(), '/') < 3) { @@ -643,11 +403,21 @@ class OC_Helper { ]; } + public static function clearStorageInfo(string $absolutePath): void { + /** @var ICacheFactory $cacheFactory */ + $cacheFactory = \OC::$server->get(ICacheFactory::class); + $memcache = $cacheFactory->createLocal('storage_info'); + $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::'; + $memcache->remove($cacheKeyPrefix . 'include'); + $memcache->remove($cacheKeyPrefix . 'exclude'); + } + /** * Returns whether the config file is set manually to read-only * @return bool + * @deprecated 32.0.0 use the `config_is_read_only` system config directly */ public static function isReadOnlyConfigEnabled() { - return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false); + return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false); } } |