@@ -19,123 +19,123 @@ |
||
| 19 | 19 | * @deprecated 14.0.0 |
| 20 | 20 | */ |
| 21 | 21 | class Files { |
| 22 | - /** |
|
| 23 | - * Recursive deletion of folders |
|
| 24 | - * |
|
| 25 | - * @param string $dir path to the folder |
|
| 26 | - * @param bool $deleteSelf if set to false only the content of the folder will be deleted |
|
| 27 | - * @return bool |
|
| 28 | - * @since 5.0.0 |
|
| 29 | - * @since 32.0.0 added the $deleteSelf parameter |
|
| 30 | - * @deprecated 14.0.0 |
|
| 31 | - */ |
|
| 32 | - public static function rmdirr($dir, bool $deleteSelf = true) { |
|
| 33 | - if (is_dir($dir)) { |
|
| 34 | - $files = new \RecursiveIteratorIterator( |
|
| 35 | - new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), |
|
| 36 | - \RecursiveIteratorIterator::CHILD_FIRST |
|
| 37 | - ); |
|
| 22 | + /** |
|
| 23 | + * Recursive deletion of folders |
|
| 24 | + * |
|
| 25 | + * @param string $dir path to the folder |
|
| 26 | + * @param bool $deleteSelf if set to false only the content of the folder will be deleted |
|
| 27 | + * @return bool |
|
| 28 | + * @since 5.0.0 |
|
| 29 | + * @since 32.0.0 added the $deleteSelf parameter |
|
| 30 | + * @deprecated 14.0.0 |
|
| 31 | + */ |
|
| 32 | + public static function rmdirr($dir, bool $deleteSelf = true) { |
|
| 33 | + if (is_dir($dir)) { |
|
| 34 | + $files = new \RecursiveIteratorIterator( |
|
| 35 | + new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), |
|
| 36 | + \RecursiveIteratorIterator::CHILD_FIRST |
|
| 37 | + ); |
|
| 38 | 38 | |
| 39 | - foreach ($files as $fileInfo) { |
|
| 40 | - /** @var \SplFileInfo $fileInfo */ |
|
| 41 | - if ($fileInfo->isLink()) { |
|
| 42 | - unlink($fileInfo->getPathname()); |
|
| 43 | - } elseif ($fileInfo->isDir()) { |
|
| 44 | - rmdir($fileInfo->getRealPath()); |
|
| 45 | - } else { |
|
| 46 | - unlink($fileInfo->getRealPath()); |
|
| 47 | - } |
|
| 48 | - } |
|
| 49 | - if ($deleteSelf) { |
|
| 50 | - rmdir($dir); |
|
| 51 | - } |
|
| 52 | - } elseif (file_exists($dir)) { |
|
| 53 | - if ($deleteSelf) { |
|
| 54 | - unlink($dir); |
|
| 55 | - } |
|
| 56 | - } |
|
| 57 | - if (!$deleteSelf) { |
|
| 58 | - return true; |
|
| 59 | - } |
|
| 39 | + foreach ($files as $fileInfo) { |
|
| 40 | + /** @var \SplFileInfo $fileInfo */ |
|
| 41 | + if ($fileInfo->isLink()) { |
|
| 42 | + unlink($fileInfo->getPathname()); |
|
| 43 | + } elseif ($fileInfo->isDir()) { |
|
| 44 | + rmdir($fileInfo->getRealPath()); |
|
| 45 | + } else { |
|
| 46 | + unlink($fileInfo->getRealPath()); |
|
| 47 | + } |
|
| 48 | + } |
|
| 49 | + if ($deleteSelf) { |
|
| 50 | + rmdir($dir); |
|
| 51 | + } |
|
| 52 | + } elseif (file_exists($dir)) { |
|
| 53 | + if ($deleteSelf) { |
|
| 54 | + unlink($dir); |
|
| 55 | + } |
|
| 56 | + } |
|
| 57 | + if (!$deleteSelf) { |
|
| 58 | + return true; |
|
| 59 | + } |
|
| 60 | 60 | |
| 61 | - return !file_exists($dir); |
|
| 62 | - } |
|
| 61 | + return !file_exists($dir); |
|
| 62 | + } |
|
| 63 | 63 | |
| 64 | - /** |
|
| 65 | - * Get the mimetype form a local file |
|
| 66 | - * @param string $path |
|
| 67 | - * @return string |
|
| 68 | - * does NOT work for ownClouds filesystem, use OC_FileSystem::getMimeType instead |
|
| 69 | - * @since 5.0.0 |
|
| 70 | - * @deprecated 14.0.0 |
|
| 71 | - */ |
|
| 72 | - public static function getMimeType($path) { |
|
| 73 | - return Server::get(IMimeTypeDetector::class)->detect($path); |
|
| 74 | - } |
|
| 64 | + /** |
|
| 65 | + * Get the mimetype form a local file |
|
| 66 | + * @param string $path |
|
| 67 | + * @return string |
|
| 68 | + * does NOT work for ownClouds filesystem, use OC_FileSystem::getMimeType instead |
|
| 69 | + * @since 5.0.0 |
|
| 70 | + * @deprecated 14.0.0 |
|
| 71 | + */ |
|
| 72 | + public static function getMimeType($path) { |
|
| 73 | + return Server::get(IMimeTypeDetector::class)->detect($path); |
|
| 74 | + } |
|
| 75 | 75 | |
| 76 | - /** |
|
| 77 | - * Search for files by mimetype |
|
| 78 | - * @param string $mimetype |
|
| 79 | - * @return array |
|
| 80 | - * @since 6.0.0 |
|
| 81 | - * @deprecated 14.0.0 |
|
| 82 | - */ |
|
| 83 | - public static function searchByMime($mimetype) { |
|
| 84 | - return \OC\Files\Filesystem::searchByMime($mimetype); |
|
| 85 | - } |
|
| 76 | + /** |
|
| 77 | + * Search for files by mimetype |
|
| 78 | + * @param string $mimetype |
|
| 79 | + * @return array |
|
| 80 | + * @since 6.0.0 |
|
| 81 | + * @deprecated 14.0.0 |
|
| 82 | + */ |
|
| 83 | + public static function searchByMime($mimetype) { |
|
| 84 | + return \OC\Files\Filesystem::searchByMime($mimetype); |
|
| 85 | + } |
|
| 86 | 86 | |
| 87 | - /** |
|
| 88 | - * Copy the contents of one stream to another |
|
| 89 | - * |
|
| 90 | - * @template T of null|true |
|
| 91 | - * @param resource $source |
|
| 92 | - * @param resource $target |
|
| 93 | - * @param T $includeResult |
|
| 94 | - * @return int|array |
|
| 95 | - * @psalm-return (T is true ? array{0: int, 1: bool} : int) |
|
| 96 | - * @since 5.0.0 |
|
| 97 | - * @since 32.0.0 added $includeResult parameter |
|
| 98 | - * @deprecated 14.0.0 |
|
| 99 | - */ |
|
| 100 | - public static function streamCopy($source, $target, ?bool $includeResult = null) { |
|
| 101 | - if (!$source || !$target) { |
|
| 102 | - return $includeResult ? [0, false] : 0; |
|
| 103 | - } |
|
| 87 | + /** |
|
| 88 | + * Copy the contents of one stream to another |
|
| 89 | + * |
|
| 90 | + * @template T of null|true |
|
| 91 | + * @param resource $source |
|
| 92 | + * @param resource $target |
|
| 93 | + * @param T $includeResult |
|
| 94 | + * @return int|array |
|
| 95 | + * @psalm-return (T is true ? array{0: int, 1: bool} : int) |
|
| 96 | + * @since 5.0.0 |
|
| 97 | + * @since 32.0.0 added $includeResult parameter |
|
| 98 | + * @deprecated 14.0.0 |
|
| 99 | + */ |
|
| 100 | + public static function streamCopy($source, $target, ?bool $includeResult = null) { |
|
| 101 | + if (!$source || !$target) { |
|
| 102 | + return $includeResult ? [0, false] : 0; |
|
| 103 | + } |
|
| 104 | 104 | |
| 105 | - $bufSize = 8192; |
|
| 106 | - $count = 0; |
|
| 107 | - $result = true; |
|
| 108 | - while (!feof($source)) { |
|
| 109 | - $buf = fread($source, $bufSize); |
|
| 110 | - if ($buf === false) { |
|
| 111 | - $result = false; |
|
| 112 | - break; |
|
| 113 | - } |
|
| 105 | + $bufSize = 8192; |
|
| 106 | + $count = 0; |
|
| 107 | + $result = true; |
|
| 108 | + while (!feof($source)) { |
|
| 109 | + $buf = fread($source, $bufSize); |
|
| 110 | + if ($buf === false) { |
|
| 111 | + $result = false; |
|
| 112 | + break; |
|
| 113 | + } |
|
| 114 | 114 | |
| 115 | - $bytesWritten = fwrite($target, $buf); |
|
| 116 | - if ($bytesWritten !== false) { |
|
| 117 | - $count += $bytesWritten; |
|
| 118 | - } |
|
| 115 | + $bytesWritten = fwrite($target, $buf); |
|
| 116 | + if ($bytesWritten !== false) { |
|
| 117 | + $count += $bytesWritten; |
|
| 118 | + } |
|
| 119 | 119 | |
| 120 | - if ($bytesWritten === false |
|
| 121 | - || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) |
|
| 122 | - ) { |
|
| 123 | - $result = false; |
|
| 124 | - break; |
|
| 125 | - } |
|
| 126 | - } |
|
| 127 | - return $includeResult ? [$count, $result] : $count; |
|
| 128 | - } |
|
| 120 | + if ($bytesWritten === false |
|
| 121 | + || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) |
|
| 122 | + ) { |
|
| 123 | + $result = false; |
|
| 124 | + break; |
|
| 125 | + } |
|
| 126 | + } |
|
| 127 | + return $includeResult ? [$count, $result] : $count; |
|
| 128 | + } |
|
| 129 | 129 | |
| 130 | - /** |
|
| 131 | - * Adds a suffix to the name in case the file exists |
|
| 132 | - * @param string $path |
|
| 133 | - * @param string $filename |
|
| 134 | - * @return string |
|
| 135 | - * @since 5.0.0 |
|
| 136 | - * @deprecated 14.0.0 use getNonExistingName of the OCP\Files\Folder object |
|
| 137 | - */ |
|
| 138 | - public static function buildNotExistingFileName($path, $filename) { |
|
| 139 | - return \OC_Helper::buildNotExistingFileName($path, $filename); |
|
| 140 | - } |
|
| 130 | + /** |
|
| 131 | + * Adds a suffix to the name in case the file exists |
|
| 132 | + * @param string $path |
|
| 133 | + * @param string $filename |
|
| 134 | + * @return string |
|
| 135 | + * @since 5.0.0 |
|
| 136 | + * @deprecated 14.0.0 use getNonExistingName of the OCP\Files\Folder object |
|
| 137 | + */ |
|
| 138 | + public static function buildNotExistingFileName($path, $filename) { |
|
| 139 | + return \OC_Helper::buildNotExistingFileName($path, $filename); |
|
| 140 | + } |
|
| 141 | 141 | } |
@@ -25,641 +25,641 @@ |
||
| 25 | 25 | * @since 4.0.0 |
| 26 | 26 | */ |
| 27 | 27 | class Util { |
| 28 | - private static ?IManager $shareManager = null; |
|
| 29 | - |
|
| 30 | - private static array $scriptsInit = []; |
|
| 31 | - private static array $scripts = []; |
|
| 32 | - private static array $scriptDeps = []; |
|
| 33 | - |
|
| 34 | - /** |
|
| 35 | - * get the current installed version of Nextcloud |
|
| 36 | - * @return array |
|
| 37 | - * @since 4.0.0 |
|
| 38 | - * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion |
|
| 39 | - */ |
|
| 40 | - public static function getVersion() { |
|
| 41 | - return Server::get(ServerVersion::class)->getVersion(); |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * @since 17.0.0 |
|
| 46 | - */ |
|
| 47 | - public static function hasExtendedSupport(): bool { |
|
| 48 | - try { |
|
| 49 | - /** @var \OCP\Support\Subscription\IRegistry */ |
|
| 50 | - $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class); |
|
| 51 | - return $subscriptionRegistry->delegateHasExtendedSupport(); |
|
| 52 | - } catch (ContainerExceptionInterface $e) { |
|
| 53 | - } |
|
| 54 | - return \OCP\Server::get(IConfig::class)->getSystemValueBool('extendedSupport', false); |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - /** |
|
| 58 | - * Set current update channel |
|
| 59 | - * @param string $channel |
|
| 60 | - * @since 8.1.0 |
|
| 61 | - */ |
|
| 62 | - public static function setChannel($channel) { |
|
| 63 | - \OCP\Server::get(IConfig::class)->setSystemValue('updater.release.channel', $channel); |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - /** |
|
| 67 | - * Get current update channel |
|
| 68 | - * @return string |
|
| 69 | - * @since 8.1.0 |
|
| 70 | - * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel |
|
| 71 | - */ |
|
| 72 | - public static function getChannel() { |
|
| 73 | - return \OCP\Server::get(ServerVersion::class)->getChannel(); |
|
| 74 | - } |
|
| 75 | - |
|
| 76 | - /** |
|
| 77 | - * check if sharing is disabled for the current user |
|
| 78 | - * |
|
| 79 | - * @return boolean |
|
| 80 | - * @since 7.0.0 |
|
| 81 | - * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser |
|
| 82 | - */ |
|
| 83 | - public static function isSharingDisabledForUser() { |
|
| 84 | - if (self::$shareManager === null) { |
|
| 85 | - self::$shareManager = Server::get(IManager::class); |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - $user = Server::get(\OCP\IUserSession::class)->getUser(); |
|
| 89 | - |
|
| 90 | - return self::$shareManager->sharingDisabledForUser($user?->getUID()); |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - /** |
|
| 94 | - * get l10n object |
|
| 95 | - * @since 6.0.0 - parameter $language was added in 8.0.0 |
|
| 96 | - */ |
|
| 97 | - public static function getL10N(string $application, ?string $language = null): IL10N { |
|
| 98 | - return Server::get(\OCP\L10N\IFactory::class)->get($application, $language); |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - /** |
|
| 102 | - * Add a css file |
|
| 103 | - * |
|
| 104 | - * @param string $application application id |
|
| 105 | - * @param ?string $file filename |
|
| 106 | - * @param bool $prepend prepend the style to the beginning of the list |
|
| 107 | - * @since 4.0.0 |
|
| 108 | - */ |
|
| 109 | - public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void { |
|
| 110 | - \OC_Util::addStyle($application, $file, $prepend); |
|
| 111 | - } |
|
| 112 | - |
|
| 113 | - /** |
|
| 114 | - * Add a standalone init js file that is loaded for initialization |
|
| 115 | - * |
|
| 116 | - * Be careful loading scripts using this method as they are loaded early |
|
| 117 | - * and block the initial page rendering. They should not have dependencies |
|
| 118 | - * on any other scripts than core-common and core-main. |
|
| 119 | - * |
|
| 120 | - * @since 28.0.0 |
|
| 121 | - */ |
|
| 122 | - public static function addInitScript(string $application, string $file): void { |
|
| 123 | - if (!empty($application)) { |
|
| 124 | - $path = "$application/js/$file"; |
|
| 125 | - } else { |
|
| 126 | - $path = "js/$file"; |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - // We need to handle the translation BEFORE the init script |
|
| 130 | - // is loaded, as the init script might use translations |
|
| 131 | - if ($application !== 'core' && !str_contains($file, 'l10n')) { |
|
| 132 | - self::addTranslations($application, null, true); |
|
| 133 | - } |
|
| 134 | - |
|
| 135 | - self::$scriptsInit[] = $path; |
|
| 136 | - } |
|
| 137 | - |
|
| 138 | - /** |
|
| 139 | - * add a javascript file |
|
| 140 | - * |
|
| 141 | - * @param string $application |
|
| 142 | - * @param string|null $file |
|
| 143 | - * @param string $afterAppId |
|
| 144 | - * @param bool $prepend |
|
| 145 | - * @since 4.0.0 |
|
| 146 | - */ |
|
| 147 | - public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void { |
|
| 148 | - if (!empty($application)) { |
|
| 149 | - $path = "$application/js/$file"; |
|
| 150 | - } else { |
|
| 151 | - $path = "js/$file"; |
|
| 152 | - } |
|
| 153 | - |
|
| 154 | - // Inject js translations if we load a script for |
|
| 155 | - // a specific app that is not core, as those js files |
|
| 156 | - // need separate handling |
|
| 157 | - if ($application !== 'core' |
|
| 158 | - && $file !== null |
|
| 159 | - && !str_contains($file, 'l10n')) { |
|
| 160 | - self::addTranslations($application); |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - // store app in dependency list |
|
| 164 | - if (!array_key_exists($application, self::$scriptDeps)) { |
|
| 165 | - self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]); |
|
| 166 | - } else { |
|
| 167 | - self::$scriptDeps[$application]->addDep($afterAppId); |
|
| 168 | - } |
|
| 169 | - |
|
| 170 | - if ($prepend) { |
|
| 171 | - array_unshift(self::$scripts[$application], $path); |
|
| 172 | - } else { |
|
| 173 | - self::$scripts[$application][] = $path; |
|
| 174 | - } |
|
| 175 | - } |
|
| 176 | - |
|
| 177 | - /** |
|
| 178 | - * Return the list of scripts injected to the page |
|
| 179 | - * |
|
| 180 | - * @return array |
|
| 181 | - * @since 24.0.0 |
|
| 182 | - */ |
|
| 183 | - public static function getScripts(): array { |
|
| 184 | - // Sort scriptDeps into sortedScriptDeps |
|
| 185 | - $scriptSort = \OCP\Server::get(AppScriptSort::class); |
|
| 186 | - $sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps); |
|
| 187 | - |
|
| 188 | - // Flatten array and remove duplicates |
|
| 189 | - $sortedScripts = array_merge([self::$scriptsInit], $sortedScripts); |
|
| 190 | - $sortedScripts = array_merge(...array_values($sortedScripts)); |
|
| 191 | - |
|
| 192 | - // Override core-common and core-main order |
|
| 193 | - if (in_array('core/js/main', $sortedScripts)) { |
|
| 194 | - array_unshift($sortedScripts, 'core/js/main'); |
|
| 195 | - } |
|
| 196 | - if (in_array('core/js/common', $sortedScripts)) { |
|
| 197 | - array_unshift($sortedScripts, 'core/js/common'); |
|
| 198 | - } |
|
| 199 | - |
|
| 200 | - return array_unique($sortedScripts); |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - /** |
|
| 204 | - * Add a translation JS file |
|
| 205 | - * @param string $application application id |
|
| 206 | - * @param string $languageCode language code, defaults to the current locale |
|
| 207 | - * @param bool $init whether the translations should be loaded early or not |
|
| 208 | - * @since 8.0.0 |
|
| 209 | - */ |
|
| 210 | - public static function addTranslations($application, $languageCode = null, $init = false) { |
|
| 211 | - if (is_null($languageCode)) { |
|
| 212 | - $languageCode = \OCP\Server::get(IFactory::class)->findLanguage($application); |
|
| 213 | - } |
|
| 214 | - if (!empty($application)) { |
|
| 215 | - $path = "$application/l10n/$languageCode"; |
|
| 216 | - } else { |
|
| 217 | - $path = "l10n/$languageCode"; |
|
| 218 | - } |
|
| 219 | - |
|
| 220 | - if ($init) { |
|
| 221 | - self::$scriptsInit[] = $path; |
|
| 222 | - } else { |
|
| 223 | - self::$scripts[$application][] = $path; |
|
| 224 | - } |
|
| 225 | - } |
|
| 226 | - |
|
| 227 | - /** |
|
| 228 | - * Add a custom element to the header |
|
| 229 | - * If $text is null then the element will be written as empty element. |
|
| 230 | - * So use "" to get a closing tag. |
|
| 231 | - * @param string $tag tag name of the element |
|
| 232 | - * @param array $attributes array of attributes for the element |
|
| 233 | - * @param string $text the text content for the element |
|
| 234 | - * @since 4.0.0 |
|
| 235 | - */ |
|
| 236 | - public static function addHeader($tag, $attributes, $text = null) { |
|
| 237 | - \OC_Util::addHeader($tag, $attributes, $text); |
|
| 238 | - } |
|
| 239 | - |
|
| 240 | - /** |
|
| 241 | - * Creates an absolute url to the given app and file. |
|
| 242 | - * @param string $app app |
|
| 243 | - * @param string $file file |
|
| 244 | - * @param array $args array with param=>value, will be appended to the returned url |
|
| 245 | - * The value of $args will be urlencoded |
|
| 246 | - * @return string the url |
|
| 247 | - * @since 4.0.0 - parameter $args was added in 4.5.0 |
|
| 248 | - */ |
|
| 249 | - public static function linkToAbsolute($app, $file, $args = []) { |
|
| 250 | - $urlGenerator = \OCP\Server::get(IURLGenerator::class); |
|
| 251 | - return $urlGenerator->getAbsoluteURL( |
|
| 252 | - $urlGenerator->linkTo($app, $file, $args) |
|
| 253 | - ); |
|
| 254 | - } |
|
| 255 | - |
|
| 256 | - /** |
|
| 257 | - * Creates an absolute url for remote use. |
|
| 258 | - * @param string $service id |
|
| 259 | - * @return string the url |
|
| 260 | - * @since 4.0.0 |
|
| 261 | - */ |
|
| 262 | - public static function linkToRemote($service) { |
|
| 263 | - $urlGenerator = \OCP\Server::get(IURLGenerator::class); |
|
| 264 | - $remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service; |
|
| 265 | - return $urlGenerator->getAbsoluteURL( |
|
| 266 | - $remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '') |
|
| 267 | - ); |
|
| 268 | - } |
|
| 269 | - |
|
| 270 | - /** |
|
| 271 | - * Returns the server host name without an eventual port number |
|
| 272 | - * @return string the server hostname |
|
| 273 | - * @since 5.0.0 |
|
| 274 | - */ |
|
| 275 | - public static function getServerHostName() { |
|
| 276 | - $host_name = \OCP\Server::get(IRequest::class)->getServerHost(); |
|
| 277 | - // strip away port number (if existing) |
|
| 278 | - $colon_pos = strpos($host_name, ':'); |
|
| 279 | - if ($colon_pos != false) { |
|
| 280 | - $host_name = substr($host_name, 0, $colon_pos); |
|
| 281 | - } |
|
| 282 | - return $host_name; |
|
| 283 | - } |
|
| 284 | - |
|
| 285 | - /** |
|
| 286 | - * Returns the default email address |
|
| 287 | - * @param string $user_part the user part of the address |
|
| 288 | - * @return string the default email address |
|
| 289 | - * |
|
| 290 | - * Assembles a default email address (using the server hostname |
|
| 291 | - * and the given user part, and returns it |
|
| 292 | - * Example: when given lostpassword-noreply as $user_part param, |
|
| 293 | - * and is currently accessed via http(s)://example.com/, |
|
| 294 | - * it would return '[email protected]' |
|
| 295 | - * |
|
| 296 | - * If the configuration value 'mail_from_address' is set in |
|
| 297 | - * config.php, this value will override the $user_part that |
|
| 298 | - * is passed to this function |
|
| 299 | - * @since 5.0.0 |
|
| 300 | - */ |
|
| 301 | - public static function getDefaultEmailAddress(string $user_part): string { |
|
| 302 | - $config = \OCP\Server::get(IConfig::class); |
|
| 303 | - $user_part = $config->getSystemValueString('mail_from_address', $user_part); |
|
| 304 | - $host_name = self::getServerHostName(); |
|
| 305 | - $host_name = $config->getSystemValueString('mail_domain', $host_name); |
|
| 306 | - $defaultEmailAddress = $user_part . '@' . $host_name; |
|
| 307 | - |
|
| 308 | - $mailer = \OCP\Server::get(IMailer::class); |
|
| 309 | - if ($mailer->validateMailAddress($defaultEmailAddress)) { |
|
| 310 | - return $defaultEmailAddress; |
|
| 311 | - } |
|
| 312 | - |
|
| 313 | - // in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain' |
|
| 314 | - return $user_part . '@localhost.localdomain'; |
|
| 315 | - } |
|
| 316 | - |
|
| 317 | - /** |
|
| 318 | - * Converts string to int of float depending on if it fits an int |
|
| 319 | - * @param numeric-string|float|int $number numeric string |
|
| 320 | - * @return int|float int if it fits, float if it is too big |
|
| 321 | - * @since 26.0.0 |
|
| 322 | - */ |
|
| 323 | - public static function numericToNumber(string|float|int $number): int|float { |
|
| 324 | - /* This is a hack to cast to (int|float) */ |
|
| 325 | - return 0 + (string)$number; |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - /** |
|
| 329 | - * Make a human file size (2048 to 2 kB) |
|
| 330 | - * @param int|float $bytes file size in bytes |
|
| 331 | - * @return string a human readable file size |
|
| 332 | - * @since 4.0.0 |
|
| 333 | - */ |
|
| 334 | - public static function humanFileSize(int|float $bytes): string { |
|
| 335 | - if ($bytes < 0) { |
|
| 336 | - return '?'; |
|
| 337 | - } |
|
| 338 | - if ($bytes < 1024) { |
|
| 339 | - return "$bytes B"; |
|
| 340 | - } |
|
| 341 | - $bytes = round($bytes / 1024, 0); |
|
| 342 | - if ($bytes < 1024) { |
|
| 343 | - return "$bytes KB"; |
|
| 344 | - } |
|
| 345 | - $bytes = round($bytes / 1024, 1); |
|
| 346 | - if ($bytes < 1024) { |
|
| 347 | - return "$bytes MB"; |
|
| 348 | - } |
|
| 349 | - $bytes = round($bytes / 1024, 1); |
|
| 350 | - if ($bytes < 1024) { |
|
| 351 | - return "$bytes GB"; |
|
| 352 | - } |
|
| 353 | - $bytes = round($bytes / 1024, 1); |
|
| 354 | - if ($bytes < 1024) { |
|
| 355 | - return "$bytes TB"; |
|
| 356 | - } |
|
| 357 | - |
|
| 358 | - $bytes = round($bytes / 1024, 1); |
|
| 359 | - return "$bytes PB"; |
|
| 360 | - } |
|
| 361 | - |
|
| 362 | - /** |
|
| 363 | - * Make a computer file size (2 kB to 2048) |
|
| 364 | - * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418 |
|
| 365 | - * |
|
| 366 | - * @param string $str file size in a fancy format |
|
| 367 | - * @return false|int|float a file size in bytes |
|
| 368 | - * @since 4.0.0 |
|
| 369 | - */ |
|
| 370 | - public static function computerFileSize(string $str): false|int|float { |
|
| 371 | - $str = strtolower($str); |
|
| 372 | - if (is_numeric($str)) { |
|
| 373 | - return Util::numericToNumber($str); |
|
| 374 | - } |
|
| 375 | - |
|
| 376 | - $bytes_array = [ |
|
| 377 | - 'b' => 1, |
|
| 378 | - 'k' => 1024, |
|
| 379 | - 'kb' => 1024, |
|
| 380 | - 'mb' => 1024 * 1024, |
|
| 381 | - 'm' => 1024 * 1024, |
|
| 382 | - 'gb' => 1024 * 1024 * 1024, |
|
| 383 | - 'g' => 1024 * 1024 * 1024, |
|
| 384 | - 'tb' => 1024 * 1024 * 1024 * 1024, |
|
| 385 | - 't' => 1024 * 1024 * 1024 * 1024, |
|
| 386 | - 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, |
|
| 387 | - 'p' => 1024 * 1024 * 1024 * 1024 * 1024, |
|
| 388 | - ]; |
|
| 389 | - |
|
| 390 | - $bytes = (float)$str; |
|
| 391 | - |
|
| 392 | - if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) { |
|
| 393 | - $bytes *= $bytes_array[$matches[1]]; |
|
| 394 | - } else { |
|
| 395 | - return false; |
|
| 396 | - } |
|
| 397 | - |
|
| 398 | - return Util::numericToNumber(round($bytes)); |
|
| 399 | - } |
|
| 400 | - |
|
| 401 | - /** |
|
| 402 | - * connects a function to a hook |
|
| 403 | - * |
|
| 404 | - * @param string $signalClass class name of emitter |
|
| 405 | - * @param string $signalName name of signal |
|
| 406 | - * @param string|object $slotClass class name of slot |
|
| 407 | - * @param string $slotName name of slot |
|
| 408 | - * @return bool |
|
| 409 | - * |
|
| 410 | - * This function makes it very easy to connect to use hooks. |
|
| 411 | - * |
|
| 412 | - * TODO: write example |
|
| 413 | - * @since 4.0.0 |
|
| 414 | - * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener |
|
| 415 | - */ |
|
| 416 | - public static function connectHook($signalClass, $signalName, $slotClass, $slotName) { |
|
| 417 | - return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName); |
|
| 418 | - } |
|
| 419 | - |
|
| 420 | - /** |
|
| 421 | - * Emits a signal. To get data from the slot use references! |
|
| 422 | - * @param string $signalclass class name of emitter |
|
| 423 | - * @param string $signalname name of signal |
|
| 424 | - * @param array $params default: array() array with additional data |
|
| 425 | - * @return bool true if slots exists or false if not |
|
| 426 | - * |
|
| 427 | - * TODO: write example |
|
| 428 | - * @since 4.0.0 |
|
| 429 | - * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent |
|
| 430 | - */ |
|
| 431 | - public static function emitHook($signalclass, $signalname, $params = []) { |
|
| 432 | - return \OC_Hook::emit($signalclass, $signalname, $params); |
|
| 433 | - } |
|
| 434 | - |
|
| 435 | - /** |
|
| 436 | - * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare |
|
| 437 | - * multiple Template elements which invoke `callRegister`. If the value |
|
| 438 | - * would not be cached these unit-tests would fail. |
|
| 439 | - * @var string |
|
| 440 | - */ |
|
| 441 | - private static $token = ''; |
|
| 442 | - |
|
| 443 | - /** |
|
| 444 | - * Register an get/post call. This is important to prevent CSRF attacks |
|
| 445 | - * @since 4.5.0 |
|
| 446 | - * @deprecated 32.0.0 directly use CsrfTokenManager instead |
|
| 447 | - */ |
|
| 448 | - public static function callRegister() { |
|
| 449 | - if (self::$token === '') { |
|
| 450 | - self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue(); |
|
| 451 | - } |
|
| 452 | - return self::$token; |
|
| 453 | - } |
|
| 454 | - |
|
| 455 | - /** |
|
| 456 | - * Used to sanitize HTML |
|
| 457 | - * |
|
| 458 | - * This function is used to sanitize HTML and should be applied on any |
|
| 459 | - * string or array of strings before displaying it on a web page. |
|
| 460 | - * |
|
| 461 | - * @param string|string[] $value |
|
| 462 | - * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter. |
|
| 463 | - * @since 4.5.0 |
|
| 464 | - */ |
|
| 465 | - public static function sanitizeHTML($value) { |
|
| 466 | - return \OC_Util::sanitizeHTML($value); |
|
| 467 | - } |
|
| 468 | - |
|
| 469 | - /** |
|
| 470 | - * Public function to encode url parameters |
|
| 471 | - * |
|
| 472 | - * This function is used to encode path to file before output. |
|
| 473 | - * Encoding is done according to RFC 3986 with one exception: |
|
| 474 | - * Character '/' is preserved as is. |
|
| 475 | - * |
|
| 476 | - * @param string $component part of URI to encode |
|
| 477 | - * @return string |
|
| 478 | - * @since 6.0.0 |
|
| 479 | - */ |
|
| 480 | - public static function encodePath($component) { |
|
| 481 | - return \OC_Util::encodePath($component); |
|
| 482 | - } |
|
| 483 | - |
|
| 484 | - /** |
|
| 485 | - * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. |
|
| 486 | - * |
|
| 487 | - * @param array $input The array to work on |
|
| 488 | - * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) |
|
| 489 | - * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 |
|
| 490 | - * @return array |
|
| 491 | - * @since 4.5.0 |
|
| 492 | - */ |
|
| 493 | - public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { |
|
| 494 | - $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; |
|
| 495 | - $ret = []; |
|
| 496 | - foreach ($input as $k => $v) { |
|
| 497 | - $ret[mb_convert_case($k, $case, $encoding)] = $v; |
|
| 498 | - } |
|
| 499 | - return $ret; |
|
| 500 | - } |
|
| 501 | - |
|
| 502 | - /** |
|
| 503 | - * performs a search in a nested array |
|
| 504 | - * |
|
| 505 | - * @param array $haystack the array to be searched |
|
| 506 | - * @param string $needle the search string |
|
| 507 | - * @param mixed $index optional, only search this key name |
|
| 508 | - * @return mixed the key of the matching field, otherwise false |
|
| 509 | - * @since 4.5.0 |
|
| 510 | - * @deprecated 15.0.0 |
|
| 511 | - */ |
|
| 512 | - public static function recursiveArraySearch($haystack, $needle, $index = null) { |
|
| 513 | - $aIt = new \RecursiveArrayIterator($haystack); |
|
| 514 | - $it = new \RecursiveIteratorIterator($aIt); |
|
| 515 | - |
|
| 516 | - while ($it->valid()) { |
|
| 517 | - if (((isset($index) && ($it->key() == $index)) || !isset($index)) && ($it->current() == $needle)) { |
|
| 518 | - return $aIt->key(); |
|
| 519 | - } |
|
| 520 | - |
|
| 521 | - $it->next(); |
|
| 522 | - } |
|
| 523 | - |
|
| 524 | - return false; |
|
| 525 | - } |
|
| 526 | - |
|
| 527 | - /** |
|
| 528 | - * calculates the maximum upload size respecting system settings, free space and user quota |
|
| 529 | - * |
|
| 530 | - * @param string $dir the current folder where the user currently operates |
|
| 531 | - * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly |
|
| 532 | - * @return int|float number of bytes representing |
|
| 533 | - * @since 5.0.0 |
|
| 534 | - */ |
|
| 535 | - public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float { |
|
| 536 | - if (is_null($free) || $free < 0) { |
|
| 537 | - $free = self::freeSpace($dir); |
|
| 538 | - } |
|
| 539 | - return min($free, self::uploadLimit()); |
|
| 540 | - } |
|
| 541 | - |
|
| 542 | - /** |
|
| 543 | - * Calculate free space left within user quota |
|
| 544 | - * @param string $dir the current folder where the user currently operates |
|
| 545 | - * @return int|float number of bytes representing |
|
| 546 | - * @since 7.0.0 |
|
| 547 | - */ |
|
| 548 | - public static function freeSpace(string $dir): int|float { |
|
| 549 | - $freeSpace = \OC\Files\Filesystem::free_space($dir); |
|
| 550 | - if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { |
|
| 551 | - $freeSpace = max($freeSpace, 0); |
|
| 552 | - return $freeSpace; |
|
| 553 | - } else { |
|
| 554 | - return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 |
|
| 555 | - } |
|
| 556 | - } |
|
| 557 | - |
|
| 558 | - /** |
|
| 559 | - * Calculate PHP upload limit |
|
| 560 | - * |
|
| 561 | - * @return int|float number of bytes representing |
|
| 562 | - * @since 7.0.0 |
|
| 563 | - */ |
|
| 564 | - public static function uploadLimit(): int|float { |
|
| 565 | - $ini = Server::get(IniGetWrapper::class); |
|
| 566 | - $upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0; |
|
| 567 | - $post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0; |
|
| 568 | - if ($upload_max_filesize === 0 && $post_max_size === 0) { |
|
| 569 | - return INF; |
|
| 570 | - } elseif ($upload_max_filesize === 0 || $post_max_size === 0) { |
|
| 571 | - return max($upload_max_filesize, $post_max_size); //only the non 0 value counts |
|
| 572 | - } else { |
|
| 573 | - return min($upload_max_filesize, $post_max_size); |
|
| 574 | - } |
|
| 575 | - } |
|
| 576 | - |
|
| 577 | - /** |
|
| 578 | - * Compare two strings to provide a natural sort |
|
| 579 | - * @param string $a first string to compare |
|
| 580 | - * @param string $b second string to compare |
|
| 581 | - * @return int -1 if $b comes before $a, 1 if $a comes before $b |
|
| 582 | - * or 0 if the strings are identical |
|
| 583 | - * @since 7.0.0 |
|
| 584 | - */ |
|
| 585 | - public static function naturalSortCompare($a, $b) { |
|
| 586 | - return \OC\NaturalSort::getInstance()->compare($a, $b); |
|
| 587 | - } |
|
| 588 | - |
|
| 589 | - /** |
|
| 590 | - * Check if a password is required for each public link |
|
| 591 | - * |
|
| 592 | - * @param bool $checkGroupMembership Check group membership exclusion |
|
| 593 | - * @return boolean |
|
| 594 | - * @since 7.0.0 |
|
| 595 | - */ |
|
| 596 | - public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { |
|
| 597 | - return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership); |
|
| 598 | - } |
|
| 599 | - |
|
| 600 | - /** |
|
| 601 | - * check if share API enforces a default expire date |
|
| 602 | - * @return boolean |
|
| 603 | - * @since 8.0.0 |
|
| 604 | - */ |
|
| 605 | - public static function isDefaultExpireDateEnforced() { |
|
| 606 | - return \OC_Util::isDefaultExpireDateEnforced(); |
|
| 607 | - } |
|
| 608 | - |
|
| 609 | - protected static $needUpgradeCache = null; |
|
| 610 | - |
|
| 611 | - /** |
|
| 612 | - * Checks whether the current version needs upgrade. |
|
| 613 | - * |
|
| 614 | - * @return bool true if upgrade is needed, false otherwise |
|
| 615 | - * @since 7.0.0 |
|
| 616 | - */ |
|
| 617 | - public static function needUpgrade() { |
|
| 618 | - if (!isset(self::$needUpgradeCache)) { |
|
| 619 | - self::$needUpgradeCache = \OC_Util::needUpgrade(\OCP\Server::get(\OC\SystemConfig::class)); |
|
| 620 | - } |
|
| 621 | - return self::$needUpgradeCache; |
|
| 622 | - } |
|
| 623 | - |
|
| 624 | - /** |
|
| 625 | - * Sometimes a string has to be shortened to fit within a certain maximum |
|
| 626 | - * data length in bytes. substr() you may break multibyte characters, |
|
| 627 | - * because it operates on single byte level. mb_substr() operates on |
|
| 628 | - * characters, so does not ensure that the shortened string satisfies the |
|
| 629 | - * max length in bytes. |
|
| 630 | - * |
|
| 631 | - * For example, json_encode is messing with multibyte characters a lot, |
|
| 632 | - * replacing them with something along "\u1234". |
|
| 633 | - * |
|
| 634 | - * This function shortens the string with by $accuracy (-5) from |
|
| 635 | - * $dataLength characters, until it fits within $dataLength bytes. |
|
| 636 | - * |
|
| 637 | - * @since 23.0.0 |
|
| 638 | - */ |
|
| 639 | - public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string { |
|
| 640 | - $temp = mb_substr($subject, 0, $dataLength); |
|
| 641 | - // json encodes encapsulates the string in double quotes, they need to be substracted |
|
| 642 | - while ((strlen(json_encode($temp)) - 2) > $dataLength) { |
|
| 643 | - $temp = mb_substr($temp, 0, -$accuracy); |
|
| 644 | - } |
|
| 645 | - return $temp; |
|
| 646 | - } |
|
| 647 | - |
|
| 648 | - /** |
|
| 649 | - * Check if a function is enabled in the php configuration |
|
| 650 | - * |
|
| 651 | - * @since 25.0.0 |
|
| 652 | - */ |
|
| 653 | - public static function isFunctionEnabled(string $functionName): bool { |
|
| 654 | - if (!function_exists($functionName)) { |
|
| 655 | - return false; |
|
| 656 | - } |
|
| 657 | - $ini = Server::get(IniGetWrapper::class); |
|
| 658 | - $disabled = explode(',', $ini->get('disable_functions') ?: ''); |
|
| 659 | - $disabled = array_map('trim', $disabled); |
|
| 660 | - if (in_array($functionName, $disabled)) { |
|
| 661 | - return false; |
|
| 662 | - } |
|
| 663 | - return true; |
|
| 664 | - } |
|
| 28 | + private static ?IManager $shareManager = null; |
|
| 29 | + |
|
| 30 | + private static array $scriptsInit = []; |
|
| 31 | + private static array $scripts = []; |
|
| 32 | + private static array $scriptDeps = []; |
|
| 33 | + |
|
| 34 | + /** |
|
| 35 | + * get the current installed version of Nextcloud |
|
| 36 | + * @return array |
|
| 37 | + * @since 4.0.0 |
|
| 38 | + * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion |
|
| 39 | + */ |
|
| 40 | + public static function getVersion() { |
|
| 41 | + return Server::get(ServerVersion::class)->getVersion(); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * @since 17.0.0 |
|
| 46 | + */ |
|
| 47 | + public static function hasExtendedSupport(): bool { |
|
| 48 | + try { |
|
| 49 | + /** @var \OCP\Support\Subscription\IRegistry */ |
|
| 50 | + $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class); |
|
| 51 | + return $subscriptionRegistry->delegateHasExtendedSupport(); |
|
| 52 | + } catch (ContainerExceptionInterface $e) { |
|
| 53 | + } |
|
| 54 | + return \OCP\Server::get(IConfig::class)->getSystemValueBool('extendedSupport', false); |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + /** |
|
| 58 | + * Set current update channel |
|
| 59 | + * @param string $channel |
|
| 60 | + * @since 8.1.0 |
|
| 61 | + */ |
|
| 62 | + public static function setChannel($channel) { |
|
| 63 | + \OCP\Server::get(IConfig::class)->setSystemValue('updater.release.channel', $channel); |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + /** |
|
| 67 | + * Get current update channel |
|
| 68 | + * @return string |
|
| 69 | + * @since 8.1.0 |
|
| 70 | + * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel |
|
| 71 | + */ |
|
| 72 | + public static function getChannel() { |
|
| 73 | + return \OCP\Server::get(ServerVersion::class)->getChannel(); |
|
| 74 | + } |
|
| 75 | + |
|
| 76 | + /** |
|
| 77 | + * check if sharing is disabled for the current user |
|
| 78 | + * |
|
| 79 | + * @return boolean |
|
| 80 | + * @since 7.0.0 |
|
| 81 | + * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser |
|
| 82 | + */ |
|
| 83 | + public static function isSharingDisabledForUser() { |
|
| 84 | + if (self::$shareManager === null) { |
|
| 85 | + self::$shareManager = Server::get(IManager::class); |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + $user = Server::get(\OCP\IUserSession::class)->getUser(); |
|
| 89 | + |
|
| 90 | + return self::$shareManager->sharingDisabledForUser($user?->getUID()); |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + /** |
|
| 94 | + * get l10n object |
|
| 95 | + * @since 6.0.0 - parameter $language was added in 8.0.0 |
|
| 96 | + */ |
|
| 97 | + public static function getL10N(string $application, ?string $language = null): IL10N { |
|
| 98 | + return Server::get(\OCP\L10N\IFactory::class)->get($application, $language); |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * Add a css file |
|
| 103 | + * |
|
| 104 | + * @param string $application application id |
|
| 105 | + * @param ?string $file filename |
|
| 106 | + * @param bool $prepend prepend the style to the beginning of the list |
|
| 107 | + * @since 4.0.0 |
|
| 108 | + */ |
|
| 109 | + public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void { |
|
| 110 | + \OC_Util::addStyle($application, $file, $prepend); |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + /** |
|
| 114 | + * Add a standalone init js file that is loaded for initialization |
|
| 115 | + * |
|
| 116 | + * Be careful loading scripts using this method as they are loaded early |
|
| 117 | + * and block the initial page rendering. They should not have dependencies |
|
| 118 | + * on any other scripts than core-common and core-main. |
|
| 119 | + * |
|
| 120 | + * @since 28.0.0 |
|
| 121 | + */ |
|
| 122 | + public static function addInitScript(string $application, string $file): void { |
|
| 123 | + if (!empty($application)) { |
|
| 124 | + $path = "$application/js/$file"; |
|
| 125 | + } else { |
|
| 126 | + $path = "js/$file"; |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + // We need to handle the translation BEFORE the init script |
|
| 130 | + // is loaded, as the init script might use translations |
|
| 131 | + if ($application !== 'core' && !str_contains($file, 'l10n')) { |
|
| 132 | + self::addTranslations($application, null, true); |
|
| 133 | + } |
|
| 134 | + |
|
| 135 | + self::$scriptsInit[] = $path; |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + /** |
|
| 139 | + * add a javascript file |
|
| 140 | + * |
|
| 141 | + * @param string $application |
|
| 142 | + * @param string|null $file |
|
| 143 | + * @param string $afterAppId |
|
| 144 | + * @param bool $prepend |
|
| 145 | + * @since 4.0.0 |
|
| 146 | + */ |
|
| 147 | + public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void { |
|
| 148 | + if (!empty($application)) { |
|
| 149 | + $path = "$application/js/$file"; |
|
| 150 | + } else { |
|
| 151 | + $path = "js/$file"; |
|
| 152 | + } |
|
| 153 | + |
|
| 154 | + // Inject js translations if we load a script for |
|
| 155 | + // a specific app that is not core, as those js files |
|
| 156 | + // need separate handling |
|
| 157 | + if ($application !== 'core' |
|
| 158 | + && $file !== null |
|
| 159 | + && !str_contains($file, 'l10n')) { |
|
| 160 | + self::addTranslations($application); |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + // store app in dependency list |
|
| 164 | + if (!array_key_exists($application, self::$scriptDeps)) { |
|
| 165 | + self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]); |
|
| 166 | + } else { |
|
| 167 | + self::$scriptDeps[$application]->addDep($afterAppId); |
|
| 168 | + } |
|
| 169 | + |
|
| 170 | + if ($prepend) { |
|
| 171 | + array_unshift(self::$scripts[$application], $path); |
|
| 172 | + } else { |
|
| 173 | + self::$scripts[$application][] = $path; |
|
| 174 | + } |
|
| 175 | + } |
|
| 176 | + |
|
| 177 | + /** |
|
| 178 | + * Return the list of scripts injected to the page |
|
| 179 | + * |
|
| 180 | + * @return array |
|
| 181 | + * @since 24.0.0 |
|
| 182 | + */ |
|
| 183 | + public static function getScripts(): array { |
|
| 184 | + // Sort scriptDeps into sortedScriptDeps |
|
| 185 | + $scriptSort = \OCP\Server::get(AppScriptSort::class); |
|
| 186 | + $sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps); |
|
| 187 | + |
|
| 188 | + // Flatten array and remove duplicates |
|
| 189 | + $sortedScripts = array_merge([self::$scriptsInit], $sortedScripts); |
|
| 190 | + $sortedScripts = array_merge(...array_values($sortedScripts)); |
|
| 191 | + |
|
| 192 | + // Override core-common and core-main order |
|
| 193 | + if (in_array('core/js/main', $sortedScripts)) { |
|
| 194 | + array_unshift($sortedScripts, 'core/js/main'); |
|
| 195 | + } |
|
| 196 | + if (in_array('core/js/common', $sortedScripts)) { |
|
| 197 | + array_unshift($sortedScripts, 'core/js/common'); |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + return array_unique($sortedScripts); |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + /** |
|
| 204 | + * Add a translation JS file |
|
| 205 | + * @param string $application application id |
|
| 206 | + * @param string $languageCode language code, defaults to the current locale |
|
| 207 | + * @param bool $init whether the translations should be loaded early or not |
|
| 208 | + * @since 8.0.0 |
|
| 209 | + */ |
|
| 210 | + public static function addTranslations($application, $languageCode = null, $init = false) { |
|
| 211 | + if (is_null($languageCode)) { |
|
| 212 | + $languageCode = \OCP\Server::get(IFactory::class)->findLanguage($application); |
|
| 213 | + } |
|
| 214 | + if (!empty($application)) { |
|
| 215 | + $path = "$application/l10n/$languageCode"; |
|
| 216 | + } else { |
|
| 217 | + $path = "l10n/$languageCode"; |
|
| 218 | + } |
|
| 219 | + |
|
| 220 | + if ($init) { |
|
| 221 | + self::$scriptsInit[] = $path; |
|
| 222 | + } else { |
|
| 223 | + self::$scripts[$application][] = $path; |
|
| 224 | + } |
|
| 225 | + } |
|
| 226 | + |
|
| 227 | + /** |
|
| 228 | + * Add a custom element to the header |
|
| 229 | + * If $text is null then the element will be written as empty element. |
|
| 230 | + * So use "" to get a closing tag. |
|
| 231 | + * @param string $tag tag name of the element |
|
| 232 | + * @param array $attributes array of attributes for the element |
|
| 233 | + * @param string $text the text content for the element |
|
| 234 | + * @since 4.0.0 |
|
| 235 | + */ |
|
| 236 | + public static function addHeader($tag, $attributes, $text = null) { |
|
| 237 | + \OC_Util::addHeader($tag, $attributes, $text); |
|
| 238 | + } |
|
| 239 | + |
|
| 240 | + /** |
|
| 241 | + * Creates an absolute url to the given app and file. |
|
| 242 | + * @param string $app app |
|
| 243 | + * @param string $file file |
|
| 244 | + * @param array $args array with param=>value, will be appended to the returned url |
|
| 245 | + * The value of $args will be urlencoded |
|
| 246 | + * @return string the url |
|
| 247 | + * @since 4.0.0 - parameter $args was added in 4.5.0 |
|
| 248 | + */ |
|
| 249 | + public static function linkToAbsolute($app, $file, $args = []) { |
|
| 250 | + $urlGenerator = \OCP\Server::get(IURLGenerator::class); |
|
| 251 | + return $urlGenerator->getAbsoluteURL( |
|
| 252 | + $urlGenerator->linkTo($app, $file, $args) |
|
| 253 | + ); |
|
| 254 | + } |
|
| 255 | + |
|
| 256 | + /** |
|
| 257 | + * Creates an absolute url for remote use. |
|
| 258 | + * @param string $service id |
|
| 259 | + * @return string the url |
|
| 260 | + * @since 4.0.0 |
|
| 261 | + */ |
|
| 262 | + public static function linkToRemote($service) { |
|
| 263 | + $urlGenerator = \OCP\Server::get(IURLGenerator::class); |
|
| 264 | + $remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service; |
|
| 265 | + return $urlGenerator->getAbsoluteURL( |
|
| 266 | + $remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '') |
|
| 267 | + ); |
|
| 268 | + } |
|
| 269 | + |
|
| 270 | + /** |
|
| 271 | + * Returns the server host name without an eventual port number |
|
| 272 | + * @return string the server hostname |
|
| 273 | + * @since 5.0.0 |
|
| 274 | + */ |
|
| 275 | + public static function getServerHostName() { |
|
| 276 | + $host_name = \OCP\Server::get(IRequest::class)->getServerHost(); |
|
| 277 | + // strip away port number (if existing) |
|
| 278 | + $colon_pos = strpos($host_name, ':'); |
|
| 279 | + if ($colon_pos != false) { |
|
| 280 | + $host_name = substr($host_name, 0, $colon_pos); |
|
| 281 | + } |
|
| 282 | + return $host_name; |
|
| 283 | + } |
|
| 284 | + |
|
| 285 | + /** |
|
| 286 | + * Returns the default email address |
|
| 287 | + * @param string $user_part the user part of the address |
|
| 288 | + * @return string the default email address |
|
| 289 | + * |
|
| 290 | + * Assembles a default email address (using the server hostname |
|
| 291 | + * and the given user part, and returns it |
|
| 292 | + * Example: when given lostpassword-noreply as $user_part param, |
|
| 293 | + * and is currently accessed via http(s)://example.com/, |
|
| 294 | + * it would return '[email protected]' |
|
| 295 | + * |
|
| 296 | + * If the configuration value 'mail_from_address' is set in |
|
| 297 | + * config.php, this value will override the $user_part that |
|
| 298 | + * is passed to this function |
|
| 299 | + * @since 5.0.0 |
|
| 300 | + */ |
|
| 301 | + public static function getDefaultEmailAddress(string $user_part): string { |
|
| 302 | + $config = \OCP\Server::get(IConfig::class); |
|
| 303 | + $user_part = $config->getSystemValueString('mail_from_address', $user_part); |
|
| 304 | + $host_name = self::getServerHostName(); |
|
| 305 | + $host_name = $config->getSystemValueString('mail_domain', $host_name); |
|
| 306 | + $defaultEmailAddress = $user_part . '@' . $host_name; |
|
| 307 | + |
|
| 308 | + $mailer = \OCP\Server::get(IMailer::class); |
|
| 309 | + if ($mailer->validateMailAddress($defaultEmailAddress)) { |
|
| 310 | + return $defaultEmailAddress; |
|
| 311 | + } |
|
| 312 | + |
|
| 313 | + // in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain' |
|
| 314 | + return $user_part . '@localhost.localdomain'; |
|
| 315 | + } |
|
| 316 | + |
|
| 317 | + /** |
|
| 318 | + * Converts string to int of float depending on if it fits an int |
|
| 319 | + * @param numeric-string|float|int $number numeric string |
|
| 320 | + * @return int|float int if it fits, float if it is too big |
|
| 321 | + * @since 26.0.0 |
|
| 322 | + */ |
|
| 323 | + public static function numericToNumber(string|float|int $number): int|float { |
|
| 324 | + /* This is a hack to cast to (int|float) */ |
|
| 325 | + return 0 + (string)$number; |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + /** |
|
| 329 | + * Make a human file size (2048 to 2 kB) |
|
| 330 | + * @param int|float $bytes file size in bytes |
|
| 331 | + * @return string a human readable file size |
|
| 332 | + * @since 4.0.0 |
|
| 333 | + */ |
|
| 334 | + public static function humanFileSize(int|float $bytes): string { |
|
| 335 | + if ($bytes < 0) { |
|
| 336 | + return '?'; |
|
| 337 | + } |
|
| 338 | + if ($bytes < 1024) { |
|
| 339 | + return "$bytes B"; |
|
| 340 | + } |
|
| 341 | + $bytes = round($bytes / 1024, 0); |
|
| 342 | + if ($bytes < 1024) { |
|
| 343 | + return "$bytes KB"; |
|
| 344 | + } |
|
| 345 | + $bytes = round($bytes / 1024, 1); |
|
| 346 | + if ($bytes < 1024) { |
|
| 347 | + return "$bytes MB"; |
|
| 348 | + } |
|
| 349 | + $bytes = round($bytes / 1024, 1); |
|
| 350 | + if ($bytes < 1024) { |
|
| 351 | + return "$bytes GB"; |
|
| 352 | + } |
|
| 353 | + $bytes = round($bytes / 1024, 1); |
|
| 354 | + if ($bytes < 1024) { |
|
| 355 | + return "$bytes TB"; |
|
| 356 | + } |
|
| 357 | + |
|
| 358 | + $bytes = round($bytes / 1024, 1); |
|
| 359 | + return "$bytes PB"; |
|
| 360 | + } |
|
| 361 | + |
|
| 362 | + /** |
|
| 363 | + * Make a computer file size (2 kB to 2048) |
|
| 364 | + * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418 |
|
| 365 | + * |
|
| 366 | + * @param string $str file size in a fancy format |
|
| 367 | + * @return false|int|float a file size in bytes |
|
| 368 | + * @since 4.0.0 |
|
| 369 | + */ |
|
| 370 | + public static function computerFileSize(string $str): false|int|float { |
|
| 371 | + $str = strtolower($str); |
|
| 372 | + if (is_numeric($str)) { |
|
| 373 | + return Util::numericToNumber($str); |
|
| 374 | + } |
|
| 375 | + |
|
| 376 | + $bytes_array = [ |
|
| 377 | + 'b' => 1, |
|
| 378 | + 'k' => 1024, |
|
| 379 | + 'kb' => 1024, |
|
| 380 | + 'mb' => 1024 * 1024, |
|
| 381 | + 'm' => 1024 * 1024, |
|
| 382 | + 'gb' => 1024 * 1024 * 1024, |
|
| 383 | + 'g' => 1024 * 1024 * 1024, |
|
| 384 | + 'tb' => 1024 * 1024 * 1024 * 1024, |
|
| 385 | + 't' => 1024 * 1024 * 1024 * 1024, |
|
| 386 | + 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, |
|
| 387 | + 'p' => 1024 * 1024 * 1024 * 1024 * 1024, |
|
| 388 | + ]; |
|
| 389 | + |
|
| 390 | + $bytes = (float)$str; |
|
| 391 | + |
|
| 392 | + if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) { |
|
| 393 | + $bytes *= $bytes_array[$matches[1]]; |
|
| 394 | + } else { |
|
| 395 | + return false; |
|
| 396 | + } |
|
| 397 | + |
|
| 398 | + return Util::numericToNumber(round($bytes)); |
|
| 399 | + } |
|
| 400 | + |
|
| 401 | + /** |
|
| 402 | + * connects a function to a hook |
|
| 403 | + * |
|
| 404 | + * @param string $signalClass class name of emitter |
|
| 405 | + * @param string $signalName name of signal |
|
| 406 | + * @param string|object $slotClass class name of slot |
|
| 407 | + * @param string $slotName name of slot |
|
| 408 | + * @return bool |
|
| 409 | + * |
|
| 410 | + * This function makes it very easy to connect to use hooks. |
|
| 411 | + * |
|
| 412 | + * TODO: write example |
|
| 413 | + * @since 4.0.0 |
|
| 414 | + * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener |
|
| 415 | + */ |
|
| 416 | + public static function connectHook($signalClass, $signalName, $slotClass, $slotName) { |
|
| 417 | + return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName); |
|
| 418 | + } |
|
| 419 | + |
|
| 420 | + /** |
|
| 421 | + * Emits a signal. To get data from the slot use references! |
|
| 422 | + * @param string $signalclass class name of emitter |
|
| 423 | + * @param string $signalname name of signal |
|
| 424 | + * @param array $params default: array() array with additional data |
|
| 425 | + * @return bool true if slots exists or false if not |
|
| 426 | + * |
|
| 427 | + * TODO: write example |
|
| 428 | + * @since 4.0.0 |
|
| 429 | + * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent |
|
| 430 | + */ |
|
| 431 | + public static function emitHook($signalclass, $signalname, $params = []) { |
|
| 432 | + return \OC_Hook::emit($signalclass, $signalname, $params); |
|
| 433 | + } |
|
| 434 | + |
|
| 435 | + /** |
|
| 436 | + * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare |
|
| 437 | + * multiple Template elements which invoke `callRegister`. If the value |
|
| 438 | + * would not be cached these unit-tests would fail. |
|
| 439 | + * @var string |
|
| 440 | + */ |
|
| 441 | + private static $token = ''; |
|
| 442 | + |
|
| 443 | + /** |
|
| 444 | + * Register an get/post call. This is important to prevent CSRF attacks |
|
| 445 | + * @since 4.5.0 |
|
| 446 | + * @deprecated 32.0.0 directly use CsrfTokenManager instead |
|
| 447 | + */ |
|
| 448 | + public static function callRegister() { |
|
| 449 | + if (self::$token === '') { |
|
| 450 | + self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue(); |
|
| 451 | + } |
|
| 452 | + return self::$token; |
|
| 453 | + } |
|
| 454 | + |
|
| 455 | + /** |
|
| 456 | + * Used to sanitize HTML |
|
| 457 | + * |
|
| 458 | + * This function is used to sanitize HTML and should be applied on any |
|
| 459 | + * string or array of strings before displaying it on a web page. |
|
| 460 | + * |
|
| 461 | + * @param string|string[] $value |
|
| 462 | + * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter. |
|
| 463 | + * @since 4.5.0 |
|
| 464 | + */ |
|
| 465 | + public static function sanitizeHTML($value) { |
|
| 466 | + return \OC_Util::sanitizeHTML($value); |
|
| 467 | + } |
|
| 468 | + |
|
| 469 | + /** |
|
| 470 | + * Public function to encode url parameters |
|
| 471 | + * |
|
| 472 | + * This function is used to encode path to file before output. |
|
| 473 | + * Encoding is done according to RFC 3986 with one exception: |
|
| 474 | + * Character '/' is preserved as is. |
|
| 475 | + * |
|
| 476 | + * @param string $component part of URI to encode |
|
| 477 | + * @return string |
|
| 478 | + * @since 6.0.0 |
|
| 479 | + */ |
|
| 480 | + public static function encodePath($component) { |
|
| 481 | + return \OC_Util::encodePath($component); |
|
| 482 | + } |
|
| 483 | + |
|
| 484 | + /** |
|
| 485 | + * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. |
|
| 486 | + * |
|
| 487 | + * @param array $input The array to work on |
|
| 488 | + * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default) |
|
| 489 | + * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8 |
|
| 490 | + * @return array |
|
| 491 | + * @since 4.5.0 |
|
| 492 | + */ |
|
| 493 | + public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { |
|
| 494 | + $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; |
|
| 495 | + $ret = []; |
|
| 496 | + foreach ($input as $k => $v) { |
|
| 497 | + $ret[mb_convert_case($k, $case, $encoding)] = $v; |
|
| 498 | + } |
|
| 499 | + return $ret; |
|
| 500 | + } |
|
| 501 | + |
|
| 502 | + /** |
|
| 503 | + * performs a search in a nested array |
|
| 504 | + * |
|
| 505 | + * @param array $haystack the array to be searched |
|
| 506 | + * @param string $needle the search string |
|
| 507 | + * @param mixed $index optional, only search this key name |
|
| 508 | + * @return mixed the key of the matching field, otherwise false |
|
| 509 | + * @since 4.5.0 |
|
| 510 | + * @deprecated 15.0.0 |
|
| 511 | + */ |
|
| 512 | + public static function recursiveArraySearch($haystack, $needle, $index = null) { |
|
| 513 | + $aIt = new \RecursiveArrayIterator($haystack); |
|
| 514 | + $it = new \RecursiveIteratorIterator($aIt); |
|
| 515 | + |
|
| 516 | + while ($it->valid()) { |
|
| 517 | + if (((isset($index) && ($it->key() == $index)) || !isset($index)) && ($it->current() == $needle)) { |
|
| 518 | + return $aIt->key(); |
|
| 519 | + } |
|
| 520 | + |
|
| 521 | + $it->next(); |
|
| 522 | + } |
|
| 523 | + |
|
| 524 | + return false; |
|
| 525 | + } |
|
| 526 | + |
|
| 527 | + /** |
|
| 528 | + * calculates the maximum upload size respecting system settings, free space and user quota |
|
| 529 | + * |
|
| 530 | + * @param string $dir the current folder where the user currently operates |
|
| 531 | + * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly |
|
| 532 | + * @return int|float number of bytes representing |
|
| 533 | + * @since 5.0.0 |
|
| 534 | + */ |
|
| 535 | + public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float { |
|
| 536 | + if (is_null($free) || $free < 0) { |
|
| 537 | + $free = self::freeSpace($dir); |
|
| 538 | + } |
|
| 539 | + return min($free, self::uploadLimit()); |
|
| 540 | + } |
|
| 541 | + |
|
| 542 | + /** |
|
| 543 | + * Calculate free space left within user quota |
|
| 544 | + * @param string $dir the current folder where the user currently operates |
|
| 545 | + * @return int|float number of bytes representing |
|
| 546 | + * @since 7.0.0 |
|
| 547 | + */ |
|
| 548 | + public static function freeSpace(string $dir): int|float { |
|
| 549 | + $freeSpace = \OC\Files\Filesystem::free_space($dir); |
|
| 550 | + if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { |
|
| 551 | + $freeSpace = max($freeSpace, 0); |
|
| 552 | + return $freeSpace; |
|
| 553 | + } else { |
|
| 554 | + return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 |
|
| 555 | + } |
|
| 556 | + } |
|
| 557 | + |
|
| 558 | + /** |
|
| 559 | + * Calculate PHP upload limit |
|
| 560 | + * |
|
| 561 | + * @return int|float number of bytes representing |
|
| 562 | + * @since 7.0.0 |
|
| 563 | + */ |
|
| 564 | + public static function uploadLimit(): int|float { |
|
| 565 | + $ini = Server::get(IniGetWrapper::class); |
|
| 566 | + $upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0; |
|
| 567 | + $post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0; |
|
| 568 | + if ($upload_max_filesize === 0 && $post_max_size === 0) { |
|
| 569 | + return INF; |
|
| 570 | + } elseif ($upload_max_filesize === 0 || $post_max_size === 0) { |
|
| 571 | + return max($upload_max_filesize, $post_max_size); //only the non 0 value counts |
|
| 572 | + } else { |
|
| 573 | + return min($upload_max_filesize, $post_max_size); |
|
| 574 | + } |
|
| 575 | + } |
|
| 576 | + |
|
| 577 | + /** |
|
| 578 | + * Compare two strings to provide a natural sort |
|
| 579 | + * @param string $a first string to compare |
|
| 580 | + * @param string $b second string to compare |
|
| 581 | + * @return int -1 if $b comes before $a, 1 if $a comes before $b |
|
| 582 | + * or 0 if the strings are identical |
|
| 583 | + * @since 7.0.0 |
|
| 584 | + */ |
|
| 585 | + public static function naturalSortCompare($a, $b) { |
|
| 586 | + return \OC\NaturalSort::getInstance()->compare($a, $b); |
|
| 587 | + } |
|
| 588 | + |
|
| 589 | + /** |
|
| 590 | + * Check if a password is required for each public link |
|
| 591 | + * |
|
| 592 | + * @param bool $checkGroupMembership Check group membership exclusion |
|
| 593 | + * @return boolean |
|
| 594 | + * @since 7.0.0 |
|
| 595 | + */ |
|
| 596 | + public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { |
|
| 597 | + return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership); |
|
| 598 | + } |
|
| 599 | + |
|
| 600 | + /** |
|
| 601 | + * check if share API enforces a default expire date |
|
| 602 | + * @return boolean |
|
| 603 | + * @since 8.0.0 |
|
| 604 | + */ |
|
| 605 | + public static function isDefaultExpireDateEnforced() { |
|
| 606 | + return \OC_Util::isDefaultExpireDateEnforced(); |
|
| 607 | + } |
|
| 608 | + |
|
| 609 | + protected static $needUpgradeCache = null; |
|
| 610 | + |
|
| 611 | + /** |
|
| 612 | + * Checks whether the current version needs upgrade. |
|
| 613 | + * |
|
| 614 | + * @return bool true if upgrade is needed, false otherwise |
|
| 615 | + * @since 7.0.0 |
|
| 616 | + */ |
|
| 617 | + public static function needUpgrade() { |
|
| 618 | + if (!isset(self::$needUpgradeCache)) { |
|
| 619 | + self::$needUpgradeCache = \OC_Util::needUpgrade(\OCP\Server::get(\OC\SystemConfig::class)); |
|
| 620 | + } |
|
| 621 | + return self::$needUpgradeCache; |
|
| 622 | + } |
|
| 623 | + |
|
| 624 | + /** |
|
| 625 | + * Sometimes a string has to be shortened to fit within a certain maximum |
|
| 626 | + * data length in bytes. substr() you may break multibyte characters, |
|
| 627 | + * because it operates on single byte level. mb_substr() operates on |
|
| 628 | + * characters, so does not ensure that the shortened string satisfies the |
|
| 629 | + * max length in bytes. |
|
| 630 | + * |
|
| 631 | + * For example, json_encode is messing with multibyte characters a lot, |
|
| 632 | + * replacing them with something along "\u1234". |
|
| 633 | + * |
|
| 634 | + * This function shortens the string with by $accuracy (-5) from |
|
| 635 | + * $dataLength characters, until it fits within $dataLength bytes. |
|
| 636 | + * |
|
| 637 | + * @since 23.0.0 |
|
| 638 | + */ |
|
| 639 | + public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string { |
|
| 640 | + $temp = mb_substr($subject, 0, $dataLength); |
|
| 641 | + // json encodes encapsulates the string in double quotes, they need to be substracted |
|
| 642 | + while ((strlen(json_encode($temp)) - 2) > $dataLength) { |
|
| 643 | + $temp = mb_substr($temp, 0, -$accuracy); |
|
| 644 | + } |
|
| 645 | + return $temp; |
|
| 646 | + } |
|
| 647 | + |
|
| 648 | + /** |
|
| 649 | + * Check if a function is enabled in the php configuration |
|
| 650 | + * |
|
| 651 | + * @since 25.0.0 |
|
| 652 | + */ |
|
| 653 | + public static function isFunctionEnabled(string $functionName): bool { |
|
| 654 | + if (!function_exists($functionName)) { |
|
| 655 | + return false; |
|
| 656 | + } |
|
| 657 | + $ini = Server::get(IniGetWrapper::class); |
|
| 658 | + $disabled = explode(',', $ini->get('disable_functions') ?: ''); |
|
| 659 | + $disabled = array_map('trim', $disabled); |
|
| 660 | + if (in_array($functionName, $disabled)) { |
|
| 661 | + return false; |
|
| 662 | + } |
|
| 663 | + return true; |
|
| 664 | + } |
|
| 665 | 665 | } |
@@ -28,435 +28,435 @@ |
||
| 28 | 28 | use function array_key_exists; |
| 29 | 29 | |
| 30 | 30 | class PreviewManager implements IPreview { |
| 31 | - protected IConfig $config; |
|
| 32 | - protected IRootFolder $rootFolder; |
|
| 33 | - protected IAppData $appData; |
|
| 34 | - protected IEventDispatcher $eventDispatcher; |
|
| 35 | - private ?Generator $generator = null; |
|
| 36 | - private GeneratorHelper $helper; |
|
| 37 | - protected bool $providerListDirty = false; |
|
| 38 | - protected bool $registeredCoreProviders = false; |
|
| 39 | - protected array $providers = []; |
|
| 40 | - |
|
| 41 | - /** @var array mime type => support status */ |
|
| 42 | - protected array $mimeTypeSupportMap = []; |
|
| 43 | - protected ?array $defaultProviders = null; |
|
| 44 | - protected ?string $userId; |
|
| 45 | - private Coordinator $bootstrapCoordinator; |
|
| 46 | - |
|
| 47 | - /** |
|
| 48 | - * Hash map (without value) of loaded bootstrap providers |
|
| 49 | - * @psalm-var array<string, null> |
|
| 50 | - */ |
|
| 51 | - private array $loadedBootstrapProviders = []; |
|
| 52 | - private ContainerInterface $container; |
|
| 53 | - private IBinaryFinder $binaryFinder; |
|
| 54 | - private IMagickSupport $imagickSupport; |
|
| 55 | - private bool $enablePreviews; |
|
| 56 | - |
|
| 57 | - public function __construct( |
|
| 58 | - IConfig $config, |
|
| 59 | - IRootFolder $rootFolder, |
|
| 60 | - IAppData $appData, |
|
| 61 | - IEventDispatcher $eventDispatcher, |
|
| 62 | - GeneratorHelper $helper, |
|
| 63 | - ?string $userId, |
|
| 64 | - Coordinator $bootstrapCoordinator, |
|
| 65 | - ContainerInterface $container, |
|
| 66 | - IBinaryFinder $binaryFinder, |
|
| 67 | - IMagickSupport $imagickSupport, |
|
| 68 | - ) { |
|
| 69 | - $this->config = $config; |
|
| 70 | - $this->rootFolder = $rootFolder; |
|
| 71 | - $this->appData = $appData; |
|
| 72 | - $this->eventDispatcher = $eventDispatcher; |
|
| 73 | - $this->helper = $helper; |
|
| 74 | - $this->userId = $userId; |
|
| 75 | - $this->bootstrapCoordinator = $bootstrapCoordinator; |
|
| 76 | - $this->container = $container; |
|
| 77 | - $this->binaryFinder = $binaryFinder; |
|
| 78 | - $this->imagickSupport = $imagickSupport; |
|
| 79 | - $this->enablePreviews = $config->getSystemValueBool('enable_previews', true); |
|
| 80 | - } |
|
| 81 | - |
|
| 82 | - /** |
|
| 83 | - * In order to improve lazy loading a closure can be registered which will be |
|
| 84 | - * called in case preview providers are actually requested |
|
| 85 | - * |
|
| 86 | - * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2 |
|
| 87 | - * |
|
| 88 | - * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider |
|
| 89 | - * @param \Closure $callable |
|
| 90 | - * @return void |
|
| 91 | - */ |
|
| 92 | - public function registerProvider($mimeTypeRegex, \Closure $callable): void { |
|
| 93 | - if (!$this->enablePreviews) { |
|
| 94 | - return; |
|
| 95 | - } |
|
| 96 | - |
|
| 97 | - if (!isset($this->providers[$mimeTypeRegex])) { |
|
| 98 | - $this->providers[$mimeTypeRegex] = []; |
|
| 99 | - } |
|
| 100 | - $this->providers[$mimeTypeRegex][] = $callable; |
|
| 101 | - $this->providerListDirty = true; |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - /** |
|
| 105 | - * Get all providers |
|
| 106 | - */ |
|
| 107 | - public function getProviders(): array { |
|
| 108 | - if (!$this->enablePreviews) { |
|
| 109 | - return []; |
|
| 110 | - } |
|
| 111 | - |
|
| 112 | - $this->registerCoreProviders(); |
|
| 113 | - $this->registerBootstrapProviders(); |
|
| 114 | - if ($this->providerListDirty) { |
|
| 115 | - $keys = array_map('strlen', array_keys($this->providers)); |
|
| 116 | - array_multisort($keys, SORT_DESC, $this->providers); |
|
| 117 | - $this->providerListDirty = false; |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - return $this->providers; |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - /** |
|
| 124 | - * Does the manager have any providers |
|
| 125 | - */ |
|
| 126 | - public function hasProviders(): bool { |
|
| 127 | - $this->registerCoreProviders(); |
|
| 128 | - return !empty($this->providers); |
|
| 129 | - } |
|
| 130 | - |
|
| 131 | - private function getGenerator(): Generator { |
|
| 132 | - if ($this->generator === null) { |
|
| 133 | - $this->generator = new Generator( |
|
| 134 | - $this->config, |
|
| 135 | - $this, |
|
| 136 | - $this->appData, |
|
| 137 | - new GeneratorHelper( |
|
| 138 | - $this->rootFolder, |
|
| 139 | - $this->config |
|
| 140 | - ), |
|
| 141 | - $this->eventDispatcher, |
|
| 142 | - $this->container->get(LoggerInterface::class), |
|
| 143 | - ); |
|
| 144 | - } |
|
| 145 | - return $this->generator; |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - public function getPreview( |
|
| 149 | - File $file, |
|
| 150 | - $width = -1, |
|
| 151 | - $height = -1, |
|
| 152 | - $crop = false, |
|
| 153 | - $mode = IPreview::MODE_FILL, |
|
| 154 | - $mimeType = null, |
|
| 155 | - bool $cacheResult = true, |
|
| 156 | - ): ISimpleFile { |
|
| 157 | - $this->throwIfPreviewsDisabled($file, $mimeType); |
|
| 158 | - $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all'); |
|
| 159 | - $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency); |
|
| 160 | - try { |
|
| 161 | - $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult); |
|
| 162 | - } finally { |
|
| 163 | - Generator::unguardWithSemaphore($sem); |
|
| 164 | - } |
|
| 165 | - |
|
| 166 | - return $preview; |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - /** |
|
| 170 | - * Generates previews of a file |
|
| 171 | - * |
|
| 172 | - * @param File $file |
|
| 173 | - * @param array $specifications |
|
| 174 | - * @param string $mimeType |
|
| 175 | - * @return ISimpleFile the last preview that was generated |
|
| 176 | - * @throws NotFoundException |
|
| 177 | - * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) |
|
| 178 | - * @since 19.0.0 |
|
| 179 | - */ |
|
| 180 | - public function generatePreviews(File $file, array $specifications, $mimeType = null) { |
|
| 181 | - $this->throwIfPreviewsDisabled($file, $mimeType); |
|
| 182 | - return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType); |
|
| 183 | - } |
|
| 184 | - |
|
| 185 | - /** |
|
| 186 | - * returns true if the passed mime type is supported |
|
| 187 | - * |
|
| 188 | - * @param string $mimeType |
|
| 189 | - * @return boolean |
|
| 190 | - */ |
|
| 191 | - public function isMimeSupported($mimeType = '*') { |
|
| 192 | - if (!$this->enablePreviews) { |
|
| 193 | - return false; |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - if (isset($this->mimeTypeSupportMap[$mimeType])) { |
|
| 197 | - return $this->mimeTypeSupportMap[$mimeType]; |
|
| 198 | - } |
|
| 199 | - |
|
| 200 | - $this->registerCoreProviders(); |
|
| 201 | - $this->registerBootstrapProviders(); |
|
| 202 | - $providerMimeTypes = array_keys($this->providers); |
|
| 203 | - foreach ($providerMimeTypes as $supportedMimeType) { |
|
| 204 | - if (preg_match($supportedMimeType, $mimeType)) { |
|
| 205 | - $this->mimeTypeSupportMap[$mimeType] = true; |
|
| 206 | - return true; |
|
| 207 | - } |
|
| 208 | - } |
|
| 209 | - $this->mimeTypeSupportMap[$mimeType] = false; |
|
| 210 | - return false; |
|
| 211 | - } |
|
| 212 | - |
|
| 213 | - /** |
|
| 214 | - * Check if a preview can be generated for a file |
|
| 215 | - */ |
|
| 216 | - public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool { |
|
| 217 | - if (!$this->enablePreviews) { |
|
| 218 | - return false; |
|
| 219 | - } |
|
| 220 | - |
|
| 221 | - $fileMimeType = $mimeType ?? $file->getMimeType(); |
|
| 222 | - |
|
| 223 | - $this->registerCoreProviders(); |
|
| 224 | - if (!$this->isMimeSupported($fileMimeType)) { |
|
| 225 | - return false; |
|
| 226 | - } |
|
| 227 | - |
|
| 228 | - $mount = $file->getMountPoint(); |
|
| 229 | - if ($mount && !$mount->getOption('previews', true)) { |
|
| 230 | - return false; |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - foreach ($this->providers as $supportedMimeType => $providers) { |
|
| 234 | - if (preg_match($supportedMimeType, $fileMimeType)) { |
|
| 235 | - foreach ($providers as $providerClosure) { |
|
| 236 | - $provider = $this->helper->getProvider($providerClosure); |
|
| 237 | - if (!($provider instanceof IProviderV2)) { |
|
| 238 | - continue; |
|
| 239 | - } |
|
| 240 | - |
|
| 241 | - if ($provider->isAvailable($file)) { |
|
| 242 | - return true; |
|
| 243 | - } |
|
| 244 | - } |
|
| 245 | - } |
|
| 246 | - } |
|
| 247 | - return false; |
|
| 248 | - } |
|
| 249 | - |
|
| 250 | - /** |
|
| 251 | - * List of enabled default providers |
|
| 252 | - * |
|
| 253 | - * The following providers are enabled by default: |
|
| 254 | - * - OC\Preview\PNG |
|
| 255 | - * - OC\Preview\JPEG |
|
| 256 | - * - OC\Preview\GIF |
|
| 257 | - * - OC\Preview\BMP |
|
| 258 | - * - OC\Preview\XBitmap |
|
| 259 | - * - OC\Preview\MarkDown |
|
| 260 | - * - OC\Preview\MP3 |
|
| 261 | - * - OC\Preview\TXT |
|
| 262 | - * |
|
| 263 | - * The following providers are disabled by default due to performance or privacy concerns: |
|
| 264 | - * - OC\Preview\Font |
|
| 265 | - * - OC\Preview\HEIC |
|
| 266 | - * - OC\Preview\Illustrator |
|
| 267 | - * - OC\Preview\Movie |
|
| 268 | - * - OC\Preview\MSOfficeDoc |
|
| 269 | - * - OC\Preview\MSOffice2003 |
|
| 270 | - * - OC\Preview\MSOffice2007 |
|
| 271 | - * - OC\Preview\OpenDocument |
|
| 272 | - * - OC\Preview\PDF |
|
| 273 | - * - OC\Preview\Photoshop |
|
| 274 | - * - OC\Preview\Postscript |
|
| 275 | - * - OC\Preview\StarOffice |
|
| 276 | - * - OC\Preview\SVG |
|
| 277 | - * - OC\Preview\TIFF |
|
| 278 | - * |
|
| 279 | - * @return array |
|
| 280 | - */ |
|
| 281 | - protected function getEnabledDefaultProvider() { |
|
| 282 | - if ($this->defaultProviders !== null) { |
|
| 283 | - return $this->defaultProviders; |
|
| 284 | - } |
|
| 285 | - |
|
| 286 | - $imageProviders = [ |
|
| 287 | - Preview\PNG::class, |
|
| 288 | - Preview\JPEG::class, |
|
| 289 | - Preview\GIF::class, |
|
| 290 | - Preview\BMP::class, |
|
| 291 | - Preview\XBitmap::class, |
|
| 292 | - Preview\Krita::class, |
|
| 293 | - Preview\WebP::class, |
|
| 294 | - ]; |
|
| 295 | - |
|
| 296 | - $this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([ |
|
| 297 | - Preview\MarkDown::class, |
|
| 298 | - Preview\MP3::class, |
|
| 299 | - Preview\TXT::class, |
|
| 300 | - Preview\OpenDocument::class, |
|
| 301 | - ], $imageProviders)); |
|
| 302 | - |
|
| 303 | - if (in_array(Preview\Image::class, $this->defaultProviders)) { |
|
| 304 | - $this->defaultProviders = array_merge($this->defaultProviders, $imageProviders); |
|
| 305 | - } |
|
| 306 | - $this->defaultProviders = array_unique($this->defaultProviders); |
|
| 307 | - return $this->defaultProviders; |
|
| 308 | - } |
|
| 309 | - |
|
| 310 | - /** |
|
| 311 | - * Register the default providers (if enabled) |
|
| 312 | - * |
|
| 313 | - * @param string $class |
|
| 314 | - * @param string $mimeType |
|
| 315 | - */ |
|
| 316 | - protected function registerCoreProvider($class, $mimeType, $options = []) { |
|
| 317 | - if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 318 | - $this->registerProvider($mimeType, function () use ($class, $options) { |
|
| 319 | - return new $class($options); |
|
| 320 | - }); |
|
| 321 | - } |
|
| 322 | - } |
|
| 323 | - |
|
| 324 | - /** |
|
| 325 | - * Register the default providers (if enabled) |
|
| 326 | - */ |
|
| 327 | - protected function registerCoreProviders() { |
|
| 328 | - if ($this->registeredCoreProviders) { |
|
| 329 | - return; |
|
| 330 | - } |
|
| 331 | - $this->registeredCoreProviders = true; |
|
| 332 | - |
|
| 333 | - $this->registerCoreProvider(Preview\TXT::class, '/text\/plain/'); |
|
| 334 | - $this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/'); |
|
| 335 | - $this->registerCoreProvider(Preview\PNG::class, '/image\/png/'); |
|
| 336 | - $this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/'); |
|
| 337 | - $this->registerCoreProvider(Preview\GIF::class, '/image\/gif/'); |
|
| 338 | - $this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/'); |
|
| 339 | - $this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/'); |
|
| 340 | - $this->registerCoreProvider(Preview\WebP::class, '/image\/webp/'); |
|
| 341 | - $this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/'); |
|
| 342 | - $this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/'); |
|
| 343 | - $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/'); |
|
| 344 | - $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes()); |
|
| 345 | - $this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes()); |
|
| 346 | - |
|
| 347 | - // SVG and Bitmap require imagick |
|
| 348 | - if ($this->imagickSupport->hasExtension()) { |
|
| 349 | - $imagickProviders = [ |
|
| 350 | - 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], |
|
| 351 | - 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], |
|
| 352 | - 'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class], |
|
| 353 | - 'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class], |
|
| 354 | - 'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class], |
|
| 355 | - 'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class], |
|
| 356 | - 'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class], |
|
| 357 | - 'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class], |
|
| 358 | - 'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class], |
|
| 359 | - 'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class], |
|
| 360 | - ]; |
|
| 361 | - |
|
| 362 | - foreach ($imagickProviders as $queryFormat => $provider) { |
|
| 363 | - $class = $provider['class']; |
|
| 364 | - if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 365 | - continue; |
|
| 366 | - } |
|
| 367 | - |
|
| 368 | - if ($this->imagickSupport->supportsFormat($queryFormat)) { |
|
| 369 | - $this->registerCoreProvider($class, $provider['mimetype']); |
|
| 370 | - } |
|
| 371 | - } |
|
| 372 | - } |
|
| 373 | - |
|
| 374 | - $this->registerCoreProvidersOffice(); |
|
| 375 | - |
|
| 376 | - // Video requires ffmpeg |
|
| 377 | - if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) { |
|
| 378 | - $movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null); |
|
| 379 | - if (!is_string($movieBinary)) { |
|
| 380 | - $movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg'); |
|
| 381 | - } |
|
| 382 | - |
|
| 383 | - |
|
| 384 | - if (is_string($movieBinary)) { |
|
| 385 | - $this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]); |
|
| 386 | - } |
|
| 387 | - } |
|
| 388 | - } |
|
| 389 | - |
|
| 390 | - private function registerCoreProvidersOffice(): void { |
|
| 391 | - $officeProviders = [ |
|
| 392 | - ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class], |
|
| 393 | - ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class], |
|
| 394 | - ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class], |
|
| 395 | - ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class], |
|
| 396 | - ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class], |
|
| 397 | - ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class], |
|
| 398 | - ]; |
|
| 399 | - |
|
| 400 | - $findBinary = true; |
|
| 401 | - $officeBinary = false; |
|
| 402 | - |
|
| 403 | - foreach ($officeProviders as $provider) { |
|
| 404 | - $class = $provider['class']; |
|
| 405 | - if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 406 | - continue; |
|
| 407 | - } |
|
| 408 | - |
|
| 409 | - if ($findBinary) { |
|
| 410 | - // Office requires openoffice or libreoffice |
|
| 411 | - $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false); |
|
| 412 | - if ($officeBinary === false) { |
|
| 413 | - $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice'); |
|
| 414 | - } |
|
| 415 | - if ($officeBinary === false) { |
|
| 416 | - $officeBinary = $this->binaryFinder->findBinaryPath('openoffice'); |
|
| 417 | - } |
|
| 418 | - $findBinary = false; |
|
| 419 | - } |
|
| 420 | - |
|
| 421 | - if ($officeBinary) { |
|
| 422 | - $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]); |
|
| 423 | - } |
|
| 424 | - } |
|
| 425 | - } |
|
| 426 | - |
|
| 427 | - private function registerBootstrapProviders(): void { |
|
| 428 | - $context = $this->bootstrapCoordinator->getRegistrationContext(); |
|
| 429 | - |
|
| 430 | - if ($context === null) { |
|
| 431 | - // Just ignore for now |
|
| 432 | - return; |
|
| 433 | - } |
|
| 434 | - |
|
| 435 | - $providers = $context->getPreviewProviders(); |
|
| 436 | - foreach ($providers as $provider) { |
|
| 437 | - $key = $provider->getMimeTypeRegex() . '-' . $provider->getService(); |
|
| 438 | - if (array_key_exists($key, $this->loadedBootstrapProviders)) { |
|
| 439 | - // Do not load the provider more than once |
|
| 440 | - continue; |
|
| 441 | - } |
|
| 442 | - $this->loadedBootstrapProviders[$key] = null; |
|
| 443 | - |
|
| 444 | - $this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider) { |
|
| 445 | - try { |
|
| 446 | - return $this->container->get($provider->getService()); |
|
| 447 | - } catch (QueryException $e) { |
|
| 448 | - return null; |
|
| 449 | - } |
|
| 450 | - }); |
|
| 451 | - } |
|
| 452 | - } |
|
| 453 | - |
|
| 454 | - /** |
|
| 455 | - * @throws NotFoundException if preview generation is disabled |
|
| 456 | - */ |
|
| 457 | - private function throwIfPreviewsDisabled(File $file, ?string $mimeType = null): void { |
|
| 458 | - if (!$this->isAvailable($file, $mimeType)) { |
|
| 459 | - throw new NotFoundException('Previews disabled'); |
|
| 460 | - } |
|
| 461 | - } |
|
| 31 | + protected IConfig $config; |
|
| 32 | + protected IRootFolder $rootFolder; |
|
| 33 | + protected IAppData $appData; |
|
| 34 | + protected IEventDispatcher $eventDispatcher; |
|
| 35 | + private ?Generator $generator = null; |
|
| 36 | + private GeneratorHelper $helper; |
|
| 37 | + protected bool $providerListDirty = false; |
|
| 38 | + protected bool $registeredCoreProviders = false; |
|
| 39 | + protected array $providers = []; |
|
| 40 | + |
|
| 41 | + /** @var array mime type => support status */ |
|
| 42 | + protected array $mimeTypeSupportMap = []; |
|
| 43 | + protected ?array $defaultProviders = null; |
|
| 44 | + protected ?string $userId; |
|
| 45 | + private Coordinator $bootstrapCoordinator; |
|
| 46 | + |
|
| 47 | + /** |
|
| 48 | + * Hash map (without value) of loaded bootstrap providers |
|
| 49 | + * @psalm-var array<string, null> |
|
| 50 | + */ |
|
| 51 | + private array $loadedBootstrapProviders = []; |
|
| 52 | + private ContainerInterface $container; |
|
| 53 | + private IBinaryFinder $binaryFinder; |
|
| 54 | + private IMagickSupport $imagickSupport; |
|
| 55 | + private bool $enablePreviews; |
|
| 56 | + |
|
| 57 | + public function __construct( |
|
| 58 | + IConfig $config, |
|
| 59 | + IRootFolder $rootFolder, |
|
| 60 | + IAppData $appData, |
|
| 61 | + IEventDispatcher $eventDispatcher, |
|
| 62 | + GeneratorHelper $helper, |
|
| 63 | + ?string $userId, |
|
| 64 | + Coordinator $bootstrapCoordinator, |
|
| 65 | + ContainerInterface $container, |
|
| 66 | + IBinaryFinder $binaryFinder, |
|
| 67 | + IMagickSupport $imagickSupport, |
|
| 68 | + ) { |
|
| 69 | + $this->config = $config; |
|
| 70 | + $this->rootFolder = $rootFolder; |
|
| 71 | + $this->appData = $appData; |
|
| 72 | + $this->eventDispatcher = $eventDispatcher; |
|
| 73 | + $this->helper = $helper; |
|
| 74 | + $this->userId = $userId; |
|
| 75 | + $this->bootstrapCoordinator = $bootstrapCoordinator; |
|
| 76 | + $this->container = $container; |
|
| 77 | + $this->binaryFinder = $binaryFinder; |
|
| 78 | + $this->imagickSupport = $imagickSupport; |
|
| 79 | + $this->enablePreviews = $config->getSystemValueBool('enable_previews', true); |
|
| 80 | + } |
|
| 81 | + |
|
| 82 | + /** |
|
| 83 | + * In order to improve lazy loading a closure can be registered which will be |
|
| 84 | + * called in case preview providers are actually requested |
|
| 85 | + * |
|
| 86 | + * $callable has to return an instance of \OCP\Preview\IProvider or \OCP\Preview\IProviderV2 |
|
| 87 | + * |
|
| 88 | + * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider |
|
| 89 | + * @param \Closure $callable |
|
| 90 | + * @return void |
|
| 91 | + */ |
|
| 92 | + public function registerProvider($mimeTypeRegex, \Closure $callable): void { |
|
| 93 | + if (!$this->enablePreviews) { |
|
| 94 | + return; |
|
| 95 | + } |
|
| 96 | + |
|
| 97 | + if (!isset($this->providers[$mimeTypeRegex])) { |
|
| 98 | + $this->providers[$mimeTypeRegex] = []; |
|
| 99 | + } |
|
| 100 | + $this->providers[$mimeTypeRegex][] = $callable; |
|
| 101 | + $this->providerListDirty = true; |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + /** |
|
| 105 | + * Get all providers |
|
| 106 | + */ |
|
| 107 | + public function getProviders(): array { |
|
| 108 | + if (!$this->enablePreviews) { |
|
| 109 | + return []; |
|
| 110 | + } |
|
| 111 | + |
|
| 112 | + $this->registerCoreProviders(); |
|
| 113 | + $this->registerBootstrapProviders(); |
|
| 114 | + if ($this->providerListDirty) { |
|
| 115 | + $keys = array_map('strlen', array_keys($this->providers)); |
|
| 116 | + array_multisort($keys, SORT_DESC, $this->providers); |
|
| 117 | + $this->providerListDirty = false; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + return $this->providers; |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + /** |
|
| 124 | + * Does the manager have any providers |
|
| 125 | + */ |
|
| 126 | + public function hasProviders(): bool { |
|
| 127 | + $this->registerCoreProviders(); |
|
| 128 | + return !empty($this->providers); |
|
| 129 | + } |
|
| 130 | + |
|
| 131 | + private function getGenerator(): Generator { |
|
| 132 | + if ($this->generator === null) { |
|
| 133 | + $this->generator = new Generator( |
|
| 134 | + $this->config, |
|
| 135 | + $this, |
|
| 136 | + $this->appData, |
|
| 137 | + new GeneratorHelper( |
|
| 138 | + $this->rootFolder, |
|
| 139 | + $this->config |
|
| 140 | + ), |
|
| 141 | + $this->eventDispatcher, |
|
| 142 | + $this->container->get(LoggerInterface::class), |
|
| 143 | + ); |
|
| 144 | + } |
|
| 145 | + return $this->generator; |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + public function getPreview( |
|
| 149 | + File $file, |
|
| 150 | + $width = -1, |
|
| 151 | + $height = -1, |
|
| 152 | + $crop = false, |
|
| 153 | + $mode = IPreview::MODE_FILL, |
|
| 154 | + $mimeType = null, |
|
| 155 | + bool $cacheResult = true, |
|
| 156 | + ): ISimpleFile { |
|
| 157 | + $this->throwIfPreviewsDisabled($file, $mimeType); |
|
| 158 | + $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all'); |
|
| 159 | + $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency); |
|
| 160 | + try { |
|
| 161 | + $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult); |
|
| 162 | + } finally { |
|
| 163 | + Generator::unguardWithSemaphore($sem); |
|
| 164 | + } |
|
| 165 | + |
|
| 166 | + return $preview; |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + /** |
|
| 170 | + * Generates previews of a file |
|
| 171 | + * |
|
| 172 | + * @param File $file |
|
| 173 | + * @param array $specifications |
|
| 174 | + * @param string $mimeType |
|
| 175 | + * @return ISimpleFile the last preview that was generated |
|
| 176 | + * @throws NotFoundException |
|
| 177 | + * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) |
|
| 178 | + * @since 19.0.0 |
|
| 179 | + */ |
|
| 180 | + public function generatePreviews(File $file, array $specifications, $mimeType = null) { |
|
| 181 | + $this->throwIfPreviewsDisabled($file, $mimeType); |
|
| 182 | + return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType); |
|
| 183 | + } |
|
| 184 | + |
|
| 185 | + /** |
|
| 186 | + * returns true if the passed mime type is supported |
|
| 187 | + * |
|
| 188 | + * @param string $mimeType |
|
| 189 | + * @return boolean |
|
| 190 | + */ |
|
| 191 | + public function isMimeSupported($mimeType = '*') { |
|
| 192 | + if (!$this->enablePreviews) { |
|
| 193 | + return false; |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + if (isset($this->mimeTypeSupportMap[$mimeType])) { |
|
| 197 | + return $this->mimeTypeSupportMap[$mimeType]; |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + $this->registerCoreProviders(); |
|
| 201 | + $this->registerBootstrapProviders(); |
|
| 202 | + $providerMimeTypes = array_keys($this->providers); |
|
| 203 | + foreach ($providerMimeTypes as $supportedMimeType) { |
|
| 204 | + if (preg_match($supportedMimeType, $mimeType)) { |
|
| 205 | + $this->mimeTypeSupportMap[$mimeType] = true; |
|
| 206 | + return true; |
|
| 207 | + } |
|
| 208 | + } |
|
| 209 | + $this->mimeTypeSupportMap[$mimeType] = false; |
|
| 210 | + return false; |
|
| 211 | + } |
|
| 212 | + |
|
| 213 | + /** |
|
| 214 | + * Check if a preview can be generated for a file |
|
| 215 | + */ |
|
| 216 | + public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool { |
|
| 217 | + if (!$this->enablePreviews) { |
|
| 218 | + return false; |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + $fileMimeType = $mimeType ?? $file->getMimeType(); |
|
| 222 | + |
|
| 223 | + $this->registerCoreProviders(); |
|
| 224 | + if (!$this->isMimeSupported($fileMimeType)) { |
|
| 225 | + return false; |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + $mount = $file->getMountPoint(); |
|
| 229 | + if ($mount && !$mount->getOption('previews', true)) { |
|
| 230 | + return false; |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + foreach ($this->providers as $supportedMimeType => $providers) { |
|
| 234 | + if (preg_match($supportedMimeType, $fileMimeType)) { |
|
| 235 | + foreach ($providers as $providerClosure) { |
|
| 236 | + $provider = $this->helper->getProvider($providerClosure); |
|
| 237 | + if (!($provider instanceof IProviderV2)) { |
|
| 238 | + continue; |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + if ($provider->isAvailable($file)) { |
|
| 242 | + return true; |
|
| 243 | + } |
|
| 244 | + } |
|
| 245 | + } |
|
| 246 | + } |
|
| 247 | + return false; |
|
| 248 | + } |
|
| 249 | + |
|
| 250 | + /** |
|
| 251 | + * List of enabled default providers |
|
| 252 | + * |
|
| 253 | + * The following providers are enabled by default: |
|
| 254 | + * - OC\Preview\PNG |
|
| 255 | + * - OC\Preview\JPEG |
|
| 256 | + * - OC\Preview\GIF |
|
| 257 | + * - OC\Preview\BMP |
|
| 258 | + * - OC\Preview\XBitmap |
|
| 259 | + * - OC\Preview\MarkDown |
|
| 260 | + * - OC\Preview\MP3 |
|
| 261 | + * - OC\Preview\TXT |
|
| 262 | + * |
|
| 263 | + * The following providers are disabled by default due to performance or privacy concerns: |
|
| 264 | + * - OC\Preview\Font |
|
| 265 | + * - OC\Preview\HEIC |
|
| 266 | + * - OC\Preview\Illustrator |
|
| 267 | + * - OC\Preview\Movie |
|
| 268 | + * - OC\Preview\MSOfficeDoc |
|
| 269 | + * - OC\Preview\MSOffice2003 |
|
| 270 | + * - OC\Preview\MSOffice2007 |
|
| 271 | + * - OC\Preview\OpenDocument |
|
| 272 | + * - OC\Preview\PDF |
|
| 273 | + * - OC\Preview\Photoshop |
|
| 274 | + * - OC\Preview\Postscript |
|
| 275 | + * - OC\Preview\StarOffice |
|
| 276 | + * - OC\Preview\SVG |
|
| 277 | + * - OC\Preview\TIFF |
|
| 278 | + * |
|
| 279 | + * @return array |
|
| 280 | + */ |
|
| 281 | + protected function getEnabledDefaultProvider() { |
|
| 282 | + if ($this->defaultProviders !== null) { |
|
| 283 | + return $this->defaultProviders; |
|
| 284 | + } |
|
| 285 | + |
|
| 286 | + $imageProviders = [ |
|
| 287 | + Preview\PNG::class, |
|
| 288 | + Preview\JPEG::class, |
|
| 289 | + Preview\GIF::class, |
|
| 290 | + Preview\BMP::class, |
|
| 291 | + Preview\XBitmap::class, |
|
| 292 | + Preview\Krita::class, |
|
| 293 | + Preview\WebP::class, |
|
| 294 | + ]; |
|
| 295 | + |
|
| 296 | + $this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([ |
|
| 297 | + Preview\MarkDown::class, |
|
| 298 | + Preview\MP3::class, |
|
| 299 | + Preview\TXT::class, |
|
| 300 | + Preview\OpenDocument::class, |
|
| 301 | + ], $imageProviders)); |
|
| 302 | + |
|
| 303 | + if (in_array(Preview\Image::class, $this->defaultProviders)) { |
|
| 304 | + $this->defaultProviders = array_merge($this->defaultProviders, $imageProviders); |
|
| 305 | + } |
|
| 306 | + $this->defaultProviders = array_unique($this->defaultProviders); |
|
| 307 | + return $this->defaultProviders; |
|
| 308 | + } |
|
| 309 | + |
|
| 310 | + /** |
|
| 311 | + * Register the default providers (if enabled) |
|
| 312 | + * |
|
| 313 | + * @param string $class |
|
| 314 | + * @param string $mimeType |
|
| 315 | + */ |
|
| 316 | + protected function registerCoreProvider($class, $mimeType, $options = []) { |
|
| 317 | + if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 318 | + $this->registerProvider($mimeType, function () use ($class, $options) { |
|
| 319 | + return new $class($options); |
|
| 320 | + }); |
|
| 321 | + } |
|
| 322 | + } |
|
| 323 | + |
|
| 324 | + /** |
|
| 325 | + * Register the default providers (if enabled) |
|
| 326 | + */ |
|
| 327 | + protected function registerCoreProviders() { |
|
| 328 | + if ($this->registeredCoreProviders) { |
|
| 329 | + return; |
|
| 330 | + } |
|
| 331 | + $this->registeredCoreProviders = true; |
|
| 332 | + |
|
| 333 | + $this->registerCoreProvider(Preview\TXT::class, '/text\/plain/'); |
|
| 334 | + $this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/'); |
|
| 335 | + $this->registerCoreProvider(Preview\PNG::class, '/image\/png/'); |
|
| 336 | + $this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/'); |
|
| 337 | + $this->registerCoreProvider(Preview\GIF::class, '/image\/gif/'); |
|
| 338 | + $this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/'); |
|
| 339 | + $this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/'); |
|
| 340 | + $this->registerCoreProvider(Preview\WebP::class, '/image\/webp/'); |
|
| 341 | + $this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/'); |
|
| 342 | + $this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/'); |
|
| 343 | + $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/'); |
|
| 344 | + $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes()); |
|
| 345 | + $this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes()); |
|
| 346 | + |
|
| 347 | + // SVG and Bitmap require imagick |
|
| 348 | + if ($this->imagickSupport->hasExtension()) { |
|
| 349 | + $imagickProviders = [ |
|
| 350 | + 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], |
|
| 351 | + 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], |
|
| 352 | + 'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class], |
|
| 353 | + 'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class], |
|
| 354 | + 'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class], |
|
| 355 | + 'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class], |
|
| 356 | + 'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class], |
|
| 357 | + 'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class], |
|
| 358 | + 'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class], |
|
| 359 | + 'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class], |
|
| 360 | + ]; |
|
| 361 | + |
|
| 362 | + foreach ($imagickProviders as $queryFormat => $provider) { |
|
| 363 | + $class = $provider['class']; |
|
| 364 | + if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 365 | + continue; |
|
| 366 | + } |
|
| 367 | + |
|
| 368 | + if ($this->imagickSupport->supportsFormat($queryFormat)) { |
|
| 369 | + $this->registerCoreProvider($class, $provider['mimetype']); |
|
| 370 | + } |
|
| 371 | + } |
|
| 372 | + } |
|
| 373 | + |
|
| 374 | + $this->registerCoreProvidersOffice(); |
|
| 375 | + |
|
| 376 | + // Video requires ffmpeg |
|
| 377 | + if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) { |
|
| 378 | + $movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null); |
|
| 379 | + if (!is_string($movieBinary)) { |
|
| 380 | + $movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg'); |
|
| 381 | + } |
|
| 382 | + |
|
| 383 | + |
|
| 384 | + if (is_string($movieBinary)) { |
|
| 385 | + $this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]); |
|
| 386 | + } |
|
| 387 | + } |
|
| 388 | + } |
|
| 389 | + |
|
| 390 | + private function registerCoreProvidersOffice(): void { |
|
| 391 | + $officeProviders = [ |
|
| 392 | + ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class], |
|
| 393 | + ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class], |
|
| 394 | + ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class], |
|
| 395 | + ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class], |
|
| 396 | + ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class], |
|
| 397 | + ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class], |
|
| 398 | + ]; |
|
| 399 | + |
|
| 400 | + $findBinary = true; |
|
| 401 | + $officeBinary = false; |
|
| 402 | + |
|
| 403 | + foreach ($officeProviders as $provider) { |
|
| 404 | + $class = $provider['class']; |
|
| 405 | + if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { |
|
| 406 | + continue; |
|
| 407 | + } |
|
| 408 | + |
|
| 409 | + if ($findBinary) { |
|
| 410 | + // Office requires openoffice or libreoffice |
|
| 411 | + $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false); |
|
| 412 | + if ($officeBinary === false) { |
|
| 413 | + $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice'); |
|
| 414 | + } |
|
| 415 | + if ($officeBinary === false) { |
|
| 416 | + $officeBinary = $this->binaryFinder->findBinaryPath('openoffice'); |
|
| 417 | + } |
|
| 418 | + $findBinary = false; |
|
| 419 | + } |
|
| 420 | + |
|
| 421 | + if ($officeBinary) { |
|
| 422 | + $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]); |
|
| 423 | + } |
|
| 424 | + } |
|
| 425 | + } |
|
| 426 | + |
|
| 427 | + private function registerBootstrapProviders(): void { |
|
| 428 | + $context = $this->bootstrapCoordinator->getRegistrationContext(); |
|
| 429 | + |
|
| 430 | + if ($context === null) { |
|
| 431 | + // Just ignore for now |
|
| 432 | + return; |
|
| 433 | + } |
|
| 434 | + |
|
| 435 | + $providers = $context->getPreviewProviders(); |
|
| 436 | + foreach ($providers as $provider) { |
|
| 437 | + $key = $provider->getMimeTypeRegex() . '-' . $provider->getService(); |
|
| 438 | + if (array_key_exists($key, $this->loadedBootstrapProviders)) { |
|
| 439 | + // Do not load the provider more than once |
|
| 440 | + continue; |
|
| 441 | + } |
|
| 442 | + $this->loadedBootstrapProviders[$key] = null; |
|
| 443 | + |
|
| 444 | + $this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider) { |
|
| 445 | + try { |
|
| 446 | + return $this->container->get($provider->getService()); |
|
| 447 | + } catch (QueryException $e) { |
|
| 448 | + return null; |
|
| 449 | + } |
|
| 450 | + }); |
|
| 451 | + } |
|
| 452 | + } |
|
| 453 | + |
|
| 454 | + /** |
|
| 455 | + * @throws NotFoundException if preview generation is disabled |
|
| 456 | + */ |
|
| 457 | + private function throwIfPreviewsDisabled(File $file, ?string $mimeType = null): void { |
|
| 458 | + if (!$this->isAvailable($file, $mimeType)) { |
|
| 459 | + throw new NotFoundException('Previews disabled'); |
|
| 460 | + } |
|
| 461 | + } |
|
| 462 | 462 | } |
@@ -35,522 +35,522 @@ |
||
| 35 | 35 | * @package OC\IntegrityCheck |
| 36 | 36 | */ |
| 37 | 37 | class Checker { |
| 38 | - public const CACHE_KEY = 'oc.integritycheck.checker'; |
|
| 39 | - |
|
| 40 | - private ICache $cache; |
|
| 41 | - |
|
| 42 | - public function __construct( |
|
| 43 | - private ServerVersion $serverVersion, |
|
| 44 | - private EnvironmentHelper $environmentHelper, |
|
| 45 | - private FileAccessHelper $fileAccessHelper, |
|
| 46 | - private ?IConfig $config, |
|
| 47 | - private ?IAppConfig $appConfig, |
|
| 48 | - ICacheFactory $cacheFactory, |
|
| 49 | - private IAppManager $appManager, |
|
| 50 | - private IMimeTypeDetector $mimeTypeDetector, |
|
| 51 | - ) { |
|
| 52 | - $this->cache = $cacheFactory->createDistributed(self::CACHE_KEY); |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - /** |
|
| 56 | - * Whether code signing is enforced or not. |
|
| 57 | - * |
|
| 58 | - * @return bool |
|
| 59 | - */ |
|
| 60 | - public function isCodeCheckEnforced(): bool { |
|
| 61 | - $notSignedChannels = [ '', 'git']; |
|
| 62 | - if (\in_array($this->serverVersion->getChannel(), $notSignedChannels, true)) { |
|
| 63 | - return false; |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - /** |
|
| 67 | - * This config option is undocumented and supposed to be so, it's only |
|
| 68 | - * applicable for very specific scenarios and we should not advertise it |
|
| 69 | - * too prominent. So please do not add it to config.sample.php. |
|
| 70 | - */ |
|
| 71 | - return !($this->config?->getSystemValueBool('integrity.check.disabled', false) ?? false); |
|
| 72 | - } |
|
| 73 | - |
|
| 74 | - /** |
|
| 75 | - * Enumerates all files belonging to the folder. Sensible defaults are excluded. |
|
| 76 | - * |
|
| 77 | - * @param string $folderToIterate |
|
| 78 | - * @param string $root |
|
| 79 | - * @return \RecursiveIteratorIterator |
|
| 80 | - * @throws \Exception |
|
| 81 | - */ |
|
| 82 | - private function getFolderIterator(string $folderToIterate, string $root = ''): \RecursiveIteratorIterator { |
|
| 83 | - $dirItr = new \RecursiveDirectoryIterator( |
|
| 84 | - $folderToIterate, |
|
| 85 | - \RecursiveDirectoryIterator::SKIP_DOTS |
|
| 86 | - ); |
|
| 87 | - if ($root === '') { |
|
| 88 | - $root = \OC::$SERVERROOT; |
|
| 89 | - } |
|
| 90 | - $root = rtrim($root, '/'); |
|
| 91 | - |
|
| 92 | - $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); |
|
| 93 | - $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator, $root); |
|
| 94 | - |
|
| 95 | - return new \RecursiveIteratorIterator( |
|
| 96 | - $excludeFoldersIterator, |
|
| 97 | - \RecursiveIteratorIterator::SELF_FIRST |
|
| 98 | - ); |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - /** |
|
| 102 | - * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found |
|
| 103 | - * in the iterator. |
|
| 104 | - * |
|
| 105 | - * @param \RecursiveIteratorIterator $iterator |
|
| 106 | - * @param string $path |
|
| 107 | - * @return array Array of hashes. |
|
| 108 | - */ |
|
| 109 | - private function generateHashes(\RecursiveIteratorIterator $iterator, |
|
| 110 | - string $path): array { |
|
| 111 | - $hashes = []; |
|
| 112 | - |
|
| 113 | - $baseDirectoryLength = \strlen($path); |
|
| 114 | - foreach ($iterator as $filename => $data) { |
|
| 115 | - /** @var \DirectoryIterator $data */ |
|
| 116 | - if ($data->isDir()) { |
|
| 117 | - continue; |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - $relativeFileName = substr($filename, $baseDirectoryLength); |
|
| 121 | - $relativeFileName = ltrim($relativeFileName, '/'); |
|
| 122 | - |
|
| 123 | - // Exclude signature.json files in the appinfo and root folder |
|
| 124 | - if ($relativeFileName === 'appinfo/signature.json') { |
|
| 125 | - continue; |
|
| 126 | - } |
|
| 127 | - // Exclude signature.json files in the appinfo and core folder |
|
| 128 | - if ($relativeFileName === 'core/signature.json') { |
|
| 129 | - continue; |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - // The .htaccess file in the root folder of ownCloud can contain |
|
| 133 | - // custom content after the installation due to the fact that dynamic |
|
| 134 | - // content is written into it at installation time as well. This |
|
| 135 | - // includes for example the 404 and 403 instructions. |
|
| 136 | - // Thus we ignore everything below the first occurrence of |
|
| 137 | - // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the |
|
| 138 | - // hash generated based on this. |
|
| 139 | - if ($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { |
|
| 140 | - $fileContent = file_get_contents($filename); |
|
| 141 | - $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); |
|
| 142 | - if (\count($explodedArray) === 2) { |
|
| 143 | - $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); |
|
| 144 | - continue; |
|
| 145 | - } |
|
| 146 | - } |
|
| 147 | - if ($filename === $this->environmentHelper->getServerRoot() . '/core/js/mimetypelist.js') { |
|
| 148 | - $oldMimetypeList = new GenerateMimetypeFileBuilder(); |
|
| 149 | - $newFile = $oldMimetypeList->generateFile($this->mimeTypeDetector->getAllAliases(), $this->mimeTypeDetector->getAllNamings()); |
|
| 150 | - $oldFile = $this->fileAccessHelper->file_get_contents($filename); |
|
| 151 | - if ($newFile === $oldFile) { |
|
| 152 | - $hashes[$relativeFileName] = hash('sha512', $oldMimetypeList->generateFile($this->mimeTypeDetector->getOnlyDefaultAliases(), $this->mimeTypeDetector->getAllNamings())); |
|
| 153 | - continue; |
|
| 154 | - } |
|
| 155 | - } |
|
| 156 | - |
|
| 157 | - $hashes[$relativeFileName] = hash_file('sha512', $filename); |
|
| 158 | - } |
|
| 159 | - |
|
| 160 | - return $hashes; |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - /** |
|
| 164 | - * Creates the signature data |
|
| 165 | - * |
|
| 166 | - * @param array $hashes |
|
| 167 | - * @param X509 $certificate |
|
| 168 | - * @param RSA $privateKey |
|
| 169 | - * @return array |
|
| 170 | - */ |
|
| 171 | - private function createSignatureData(array $hashes, |
|
| 172 | - X509 $certificate, |
|
| 173 | - RSA $privateKey): array { |
|
| 174 | - ksort($hashes); |
|
| 175 | - |
|
| 176 | - $privateKey->setSignatureMode(RSA::SIGNATURE_PSS); |
|
| 177 | - $privateKey->setMGFHash('sha512'); |
|
| 178 | - // See https://tools.ietf.org/html/rfc3447#page-38 |
|
| 179 | - $privateKey->setSaltLength(0); |
|
| 180 | - $signature = $privateKey->sign(json_encode($hashes)); |
|
| 181 | - |
|
| 182 | - return [ |
|
| 183 | - 'hashes' => $hashes, |
|
| 184 | - 'signature' => base64_encode($signature), |
|
| 185 | - 'certificate' => $certificate->saveX509($certificate->currentCert), |
|
| 186 | - ]; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - /** |
|
| 190 | - * Write the signature of the app in the specified folder |
|
| 191 | - * |
|
| 192 | - * @param string $path |
|
| 193 | - * @param X509 $certificate |
|
| 194 | - * @param RSA $privateKey |
|
| 195 | - * @throws \Exception |
|
| 196 | - */ |
|
| 197 | - public function writeAppSignature($path, |
|
| 198 | - X509 $certificate, |
|
| 199 | - RSA $privateKey) { |
|
| 200 | - $appInfoDir = $path . '/appinfo'; |
|
| 201 | - try { |
|
| 202 | - $this->fileAccessHelper->assertDirectoryExists($appInfoDir); |
|
| 203 | - |
|
| 204 | - $iterator = $this->getFolderIterator($path); |
|
| 205 | - $hashes = $this->generateHashes($iterator, $path); |
|
| 206 | - $signature = $this->createSignatureData($hashes, $certificate, $privateKey); |
|
| 207 | - $this->fileAccessHelper->file_put_contents( |
|
| 208 | - $appInfoDir . '/signature.json', |
|
| 209 | - json_encode($signature, JSON_PRETTY_PRINT) |
|
| 210 | - ); |
|
| 211 | - } catch (\Exception $e) { |
|
| 212 | - if (!$this->fileAccessHelper->is_writable($appInfoDir)) { |
|
| 213 | - throw new \Exception($appInfoDir . ' is not writable'); |
|
| 214 | - } |
|
| 215 | - throw $e; |
|
| 216 | - } |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - /** |
|
| 220 | - * Write the signature of core |
|
| 221 | - * |
|
| 222 | - * @param X509 $certificate |
|
| 223 | - * @param RSA $rsa |
|
| 224 | - * @param string $path |
|
| 225 | - * @throws \Exception |
|
| 226 | - */ |
|
| 227 | - public function writeCoreSignature(X509 $certificate, |
|
| 228 | - RSA $rsa, |
|
| 229 | - $path) { |
|
| 230 | - $coreDir = $path . '/core'; |
|
| 231 | - try { |
|
| 232 | - $this->fileAccessHelper->assertDirectoryExists($coreDir); |
|
| 233 | - $iterator = $this->getFolderIterator($path, $path); |
|
| 234 | - $hashes = $this->generateHashes($iterator, $path); |
|
| 235 | - $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); |
|
| 236 | - $this->fileAccessHelper->file_put_contents( |
|
| 237 | - $coreDir . '/signature.json', |
|
| 238 | - json_encode($signatureData, JSON_PRETTY_PRINT) |
|
| 239 | - ); |
|
| 240 | - } catch (\Exception $e) { |
|
| 241 | - if (!$this->fileAccessHelper->is_writable($coreDir)) { |
|
| 242 | - throw new \Exception($coreDir . ' is not writable'); |
|
| 243 | - } |
|
| 244 | - throw $e; |
|
| 245 | - } |
|
| 246 | - } |
|
| 247 | - |
|
| 248 | - /** |
|
| 249 | - * Split the certificate file in individual certs |
|
| 250 | - * |
|
| 251 | - * @param string $cert |
|
| 252 | - * @return string[] |
|
| 253 | - */ |
|
| 254 | - private function splitCerts(string $cert): array { |
|
| 255 | - preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches); |
|
| 256 | - |
|
| 257 | - return $matches[0]; |
|
| 258 | - } |
|
| 259 | - |
|
| 260 | - /** |
|
| 261 | - * Verifies the signature for the specified path. |
|
| 262 | - * |
|
| 263 | - * @param string $signaturePath |
|
| 264 | - * @param string $basePath |
|
| 265 | - * @param string $certificateCN |
|
| 266 | - * @param bool $forceVerify |
|
| 267 | - * @return array |
|
| 268 | - * @throws InvalidSignatureException |
|
| 269 | - * @throws \Exception |
|
| 270 | - */ |
|
| 271 | - private function verify(string $signaturePath, string $basePath, string $certificateCN, bool $forceVerify = false): array { |
|
| 272 | - if (!$forceVerify && !$this->isCodeCheckEnforced()) { |
|
| 273 | - return []; |
|
| 274 | - } |
|
| 275 | - |
|
| 276 | - $content = $this->fileAccessHelper->file_get_contents($signaturePath); |
|
| 277 | - $signatureData = null; |
|
| 278 | - |
|
| 279 | - if (\is_string($content)) { |
|
| 280 | - $signatureData = json_decode($content, true); |
|
| 281 | - } |
|
| 282 | - if (!\is_array($signatureData)) { |
|
| 283 | - throw new InvalidSignatureException('Signature data not found.'); |
|
| 284 | - } |
|
| 285 | - |
|
| 286 | - $expectedHashes = $signatureData['hashes']; |
|
| 287 | - ksort($expectedHashes); |
|
| 288 | - $signature = base64_decode($signatureData['signature']); |
|
| 289 | - $certificate = $signatureData['certificate']; |
|
| 290 | - |
|
| 291 | - // Check if certificate is signed by Nextcloud Root Authority |
|
| 292 | - $x509 = new \phpseclib\File\X509(); |
|
| 293 | - $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot() . '/resources/codesigning/root.crt'); |
|
| 294 | - |
|
| 295 | - $rootCerts = $this->splitCerts($rootCertificatePublicKey); |
|
| 296 | - foreach ($rootCerts as $rootCert) { |
|
| 297 | - $x509->loadCA($rootCert); |
|
| 298 | - } |
|
| 299 | - $x509->loadX509($certificate); |
|
| 300 | - if (!$x509->validateSignature()) { |
|
| 301 | - throw new InvalidSignatureException('Certificate is not valid.'); |
|
| 302 | - } |
|
| 303 | - // Verify if certificate has proper CN. "core" CN is always trusted. |
|
| 304 | - if ($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') { |
|
| 305 | - throw new InvalidSignatureException( |
|
| 306 | - sprintf('Certificate is not valid for required scope. (Requested: %s, current: CN=%s)', $certificateCN, $x509->getDN(true)['CN']) |
|
| 307 | - ); |
|
| 308 | - } |
|
| 309 | - |
|
| 310 | - // Check if the signature of the files is valid |
|
| 311 | - $rsa = new \phpseclib\Crypt\RSA(); |
|
| 312 | - $rsa->loadKey($x509->currentCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']); |
|
| 313 | - $rsa->setSignatureMode(RSA::SIGNATURE_PSS); |
|
| 314 | - $rsa->setMGFHash('sha512'); |
|
| 315 | - // See https://tools.ietf.org/html/rfc3447#page-38 |
|
| 316 | - $rsa->setSaltLength(0); |
|
| 317 | - if (!$rsa->verify(json_encode($expectedHashes), $signature)) { |
|
| 318 | - throw new InvalidSignatureException('Signature could not get verified.'); |
|
| 319 | - } |
|
| 320 | - |
|
| 321 | - // Fixes for the updater as shipped with ownCloud 9.0.x: The updater is |
|
| 322 | - // replaced after the code integrity check is performed. |
|
| 323 | - // |
|
| 324 | - // Due to this reason we exclude the whole updater/ folder from the code |
|
| 325 | - // integrity check. |
|
| 326 | - if ($basePath === $this->environmentHelper->getServerRoot()) { |
|
| 327 | - foreach ($expectedHashes as $fileName => $hash) { |
|
| 328 | - if (str_starts_with($fileName, 'updater/')) { |
|
| 329 | - unset($expectedHashes[$fileName]); |
|
| 330 | - } |
|
| 331 | - } |
|
| 332 | - } |
|
| 333 | - |
|
| 334 | - // Compare the list of files which are not identical |
|
| 335 | - $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($basePath), $basePath); |
|
| 336 | - $differencesA = array_diff_assoc($expectedHashes, $currentInstanceHashes); |
|
| 337 | - $differencesB = array_diff_assoc($currentInstanceHashes, $expectedHashes); |
|
| 338 | - $differences = array_unique(array_merge($differencesA, $differencesB)); |
|
| 339 | - $differenceArray = []; |
|
| 340 | - foreach ($differences as $filename => $hash) { |
|
| 341 | - // Check if file should not exist in the new signature table |
|
| 342 | - if (!array_key_exists($filename, $expectedHashes)) { |
|
| 343 | - $differenceArray['EXTRA_FILE'][$filename]['expected'] = ''; |
|
| 344 | - $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash; |
|
| 345 | - continue; |
|
| 346 | - } |
|
| 347 | - |
|
| 348 | - // Check if file is missing |
|
| 349 | - if (!array_key_exists($filename, $currentInstanceHashes)) { |
|
| 350 | - $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes[$filename]; |
|
| 351 | - $differenceArray['FILE_MISSING'][$filename]['current'] = ''; |
|
| 352 | - continue; |
|
| 353 | - } |
|
| 354 | - |
|
| 355 | - // Check if hash does mismatch |
|
| 356 | - if ($expectedHashes[$filename] !== $currentInstanceHashes[$filename]) { |
|
| 357 | - $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes[$filename]; |
|
| 358 | - $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename]; |
|
| 359 | - continue; |
|
| 360 | - } |
|
| 361 | - |
|
| 362 | - // Should never happen. |
|
| 363 | - throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.'); |
|
| 364 | - } |
|
| 365 | - |
|
| 366 | - return $differenceArray; |
|
| 367 | - } |
|
| 368 | - |
|
| 369 | - /** |
|
| 370 | - * Whether the code integrity check has passed successful or not |
|
| 371 | - * |
|
| 372 | - * @return bool |
|
| 373 | - */ |
|
| 374 | - public function hasPassedCheck(): bool { |
|
| 375 | - $results = $this->getResults(); |
|
| 376 | - if ($results !== null && empty($results)) { |
|
| 377 | - return true; |
|
| 378 | - } |
|
| 379 | - |
|
| 380 | - return false; |
|
| 381 | - } |
|
| 382 | - |
|
| 383 | - /** |
|
| 384 | - * @return array|null Either the results or null if no results available |
|
| 385 | - */ |
|
| 386 | - public function getResults(): ?array { |
|
| 387 | - $cachedResults = $this->cache->get(self::CACHE_KEY); |
|
| 388 | - if (!\is_null($cachedResults) && $cachedResults !== false) { |
|
| 389 | - return json_decode($cachedResults, true); |
|
| 390 | - } |
|
| 391 | - |
|
| 392 | - if ($this->appConfig?->hasKey('core', self::CACHE_KEY, lazy: true)) { |
|
| 393 | - return $this->appConfig->getValueArray('core', self::CACHE_KEY, lazy: true); |
|
| 394 | - } |
|
| 395 | - |
|
| 396 | - // No results available |
|
| 397 | - return null; |
|
| 398 | - } |
|
| 399 | - |
|
| 400 | - /** |
|
| 401 | - * Stores the results in the app config as well as cache |
|
| 402 | - * |
|
| 403 | - * @param string $scope |
|
| 404 | - * @param array $result |
|
| 405 | - */ |
|
| 406 | - private function storeResults(string $scope, array $result) { |
|
| 407 | - $resultArray = $this->getResults() ?? []; |
|
| 408 | - unset($resultArray[$scope]); |
|
| 409 | - if (!empty($result)) { |
|
| 410 | - $resultArray[$scope] = $result; |
|
| 411 | - } |
|
| 412 | - $this->appConfig?->setValueArray('core', self::CACHE_KEY, $resultArray, lazy: true); |
|
| 413 | - $this->cache->set(self::CACHE_KEY, json_encode($resultArray)); |
|
| 414 | - } |
|
| 415 | - |
|
| 416 | - /** |
|
| 417 | - * |
|
| 418 | - * Clean previous results for a proper rescanning. Otherwise |
|
| 419 | - */ |
|
| 420 | - private function cleanResults() { |
|
| 421 | - $this->appConfig->deleteKey('core', self::CACHE_KEY); |
|
| 422 | - $this->cache->remove(self::CACHE_KEY); |
|
| 423 | - } |
|
| 424 | - |
|
| 425 | - /** |
|
| 426 | - * Verify the signature of $appId. Returns an array with the following content: |
|
| 427 | - * [ |
|
| 428 | - * 'FILE_MISSING' => |
|
| 429 | - * [ |
|
| 430 | - * 'filename' => [ |
|
| 431 | - * 'expected' => 'expectedSHA512', |
|
| 432 | - * 'current' => 'currentSHA512', |
|
| 433 | - * ], |
|
| 434 | - * ], |
|
| 435 | - * 'EXTRA_FILE' => |
|
| 436 | - * [ |
|
| 437 | - * 'filename' => [ |
|
| 438 | - * 'expected' => 'expectedSHA512', |
|
| 439 | - * 'current' => 'currentSHA512', |
|
| 440 | - * ], |
|
| 441 | - * ], |
|
| 442 | - * 'INVALID_HASH' => |
|
| 443 | - * [ |
|
| 444 | - * 'filename' => [ |
|
| 445 | - * 'expected' => 'expectedSHA512', |
|
| 446 | - * 'current' => 'currentSHA512', |
|
| 447 | - * ], |
|
| 448 | - * ], |
|
| 449 | - * ] |
|
| 450 | - * |
|
| 451 | - * Array may be empty in case no problems have been found. |
|
| 452 | - * |
|
| 453 | - * @param string $appId |
|
| 454 | - * @param string $path Optional path. If none is given it will be guessed. |
|
| 455 | - * @param bool $forceVerify |
|
| 456 | - * @return array |
|
| 457 | - */ |
|
| 458 | - public function verifyAppSignature(string $appId, string $path = '', bool $forceVerify = false): array { |
|
| 459 | - try { |
|
| 460 | - if ($path === '') { |
|
| 461 | - $path = $this->appManager->getAppPath($appId); |
|
| 462 | - } |
|
| 463 | - $result = $this->verify( |
|
| 464 | - $path . '/appinfo/signature.json', |
|
| 465 | - $path, |
|
| 466 | - $appId, |
|
| 467 | - $forceVerify |
|
| 468 | - ); |
|
| 469 | - } catch (\Exception $e) { |
|
| 470 | - $result = [ |
|
| 471 | - 'EXCEPTION' => [ |
|
| 472 | - 'class' => \get_class($e), |
|
| 473 | - 'message' => $e->getMessage(), |
|
| 474 | - ], |
|
| 475 | - ]; |
|
| 476 | - } |
|
| 477 | - $this->storeResults($appId, $result); |
|
| 478 | - |
|
| 479 | - return $result; |
|
| 480 | - } |
|
| 481 | - |
|
| 482 | - /** |
|
| 483 | - * Verify the signature of core. Returns an array with the following content: |
|
| 484 | - * [ |
|
| 485 | - * 'FILE_MISSING' => |
|
| 486 | - * [ |
|
| 487 | - * 'filename' => [ |
|
| 488 | - * 'expected' => 'expectedSHA512', |
|
| 489 | - * 'current' => 'currentSHA512', |
|
| 490 | - * ], |
|
| 491 | - * ], |
|
| 492 | - * 'EXTRA_FILE' => |
|
| 493 | - * [ |
|
| 494 | - * 'filename' => [ |
|
| 495 | - * 'expected' => 'expectedSHA512', |
|
| 496 | - * 'current' => 'currentSHA512', |
|
| 497 | - * ], |
|
| 498 | - * ], |
|
| 499 | - * 'INVALID_HASH' => |
|
| 500 | - * [ |
|
| 501 | - * 'filename' => [ |
|
| 502 | - * 'expected' => 'expectedSHA512', |
|
| 503 | - * 'current' => 'currentSHA512', |
|
| 504 | - * ], |
|
| 505 | - * ], |
|
| 506 | - * ] |
|
| 507 | - * |
|
| 508 | - * Array may be empty in case no problems have been found. |
|
| 509 | - * |
|
| 510 | - * @return array |
|
| 511 | - */ |
|
| 512 | - public function verifyCoreSignature(): array { |
|
| 513 | - try { |
|
| 514 | - $result = $this->verify( |
|
| 515 | - $this->environmentHelper->getServerRoot() . '/core/signature.json', |
|
| 516 | - $this->environmentHelper->getServerRoot(), |
|
| 517 | - 'core' |
|
| 518 | - ); |
|
| 519 | - } catch (\Exception $e) { |
|
| 520 | - $result = [ |
|
| 521 | - 'EXCEPTION' => [ |
|
| 522 | - 'class' => \get_class($e), |
|
| 523 | - 'message' => $e->getMessage(), |
|
| 524 | - ], |
|
| 525 | - ]; |
|
| 526 | - } |
|
| 527 | - $this->storeResults('core', $result); |
|
| 528 | - |
|
| 529 | - return $result; |
|
| 530 | - } |
|
| 531 | - |
|
| 532 | - /** |
|
| 533 | - * Verify the core code of the instance as well as all applicable applications |
|
| 534 | - * and store the results. |
|
| 535 | - */ |
|
| 536 | - public function runInstanceVerification() { |
|
| 537 | - $this->cleanResults(); |
|
| 538 | - $this->verifyCoreSignature(); |
|
| 539 | - $appIds = $this->appManager->getAllAppsInAppsFolders(); |
|
| 540 | - foreach ($appIds as $appId) { |
|
| 541 | - // If an application is shipped a valid signature is required |
|
| 542 | - $isShipped = $this->appManager->isShipped($appId); |
|
| 543 | - $appNeedsToBeChecked = false; |
|
| 544 | - if ($isShipped) { |
|
| 545 | - $appNeedsToBeChecked = true; |
|
| 546 | - } elseif ($this->fileAccessHelper->file_exists($this->appManager->getAppPath($appId) . '/appinfo/signature.json')) { |
|
| 547 | - // Otherwise only if the application explicitly ships a signature.json file |
|
| 548 | - $appNeedsToBeChecked = true; |
|
| 549 | - } |
|
| 550 | - |
|
| 551 | - if ($appNeedsToBeChecked) { |
|
| 552 | - $this->verifyAppSignature($appId); |
|
| 553 | - } |
|
| 554 | - } |
|
| 555 | - } |
|
| 38 | + public const CACHE_KEY = 'oc.integritycheck.checker'; |
|
| 39 | + |
|
| 40 | + private ICache $cache; |
|
| 41 | + |
|
| 42 | + public function __construct( |
|
| 43 | + private ServerVersion $serverVersion, |
|
| 44 | + private EnvironmentHelper $environmentHelper, |
|
| 45 | + private FileAccessHelper $fileAccessHelper, |
|
| 46 | + private ?IConfig $config, |
|
| 47 | + private ?IAppConfig $appConfig, |
|
| 48 | + ICacheFactory $cacheFactory, |
|
| 49 | + private IAppManager $appManager, |
|
| 50 | + private IMimeTypeDetector $mimeTypeDetector, |
|
| 51 | + ) { |
|
| 52 | + $this->cache = $cacheFactory->createDistributed(self::CACHE_KEY); |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + /** |
|
| 56 | + * Whether code signing is enforced or not. |
|
| 57 | + * |
|
| 58 | + * @return bool |
|
| 59 | + */ |
|
| 60 | + public function isCodeCheckEnforced(): bool { |
|
| 61 | + $notSignedChannels = [ '', 'git']; |
|
| 62 | + if (\in_array($this->serverVersion->getChannel(), $notSignedChannels, true)) { |
|
| 63 | + return false; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + /** |
|
| 67 | + * This config option is undocumented and supposed to be so, it's only |
|
| 68 | + * applicable for very specific scenarios and we should not advertise it |
|
| 69 | + * too prominent. So please do not add it to config.sample.php. |
|
| 70 | + */ |
|
| 71 | + return !($this->config?->getSystemValueBool('integrity.check.disabled', false) ?? false); |
|
| 72 | + } |
|
| 73 | + |
|
| 74 | + /** |
|
| 75 | + * Enumerates all files belonging to the folder. Sensible defaults are excluded. |
|
| 76 | + * |
|
| 77 | + * @param string $folderToIterate |
|
| 78 | + * @param string $root |
|
| 79 | + * @return \RecursiveIteratorIterator |
|
| 80 | + * @throws \Exception |
|
| 81 | + */ |
|
| 82 | + private function getFolderIterator(string $folderToIterate, string $root = ''): \RecursiveIteratorIterator { |
|
| 83 | + $dirItr = new \RecursiveDirectoryIterator( |
|
| 84 | + $folderToIterate, |
|
| 85 | + \RecursiveDirectoryIterator::SKIP_DOTS |
|
| 86 | + ); |
|
| 87 | + if ($root === '') { |
|
| 88 | + $root = \OC::$SERVERROOT; |
|
| 89 | + } |
|
| 90 | + $root = rtrim($root, '/'); |
|
| 91 | + |
|
| 92 | + $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); |
|
| 93 | + $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator, $root); |
|
| 94 | + |
|
| 95 | + return new \RecursiveIteratorIterator( |
|
| 96 | + $excludeFoldersIterator, |
|
| 97 | + \RecursiveIteratorIterator::SELF_FIRST |
|
| 98 | + ); |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found |
|
| 103 | + * in the iterator. |
|
| 104 | + * |
|
| 105 | + * @param \RecursiveIteratorIterator $iterator |
|
| 106 | + * @param string $path |
|
| 107 | + * @return array Array of hashes. |
|
| 108 | + */ |
|
| 109 | + private function generateHashes(\RecursiveIteratorIterator $iterator, |
|
| 110 | + string $path): array { |
|
| 111 | + $hashes = []; |
|
| 112 | + |
|
| 113 | + $baseDirectoryLength = \strlen($path); |
|
| 114 | + foreach ($iterator as $filename => $data) { |
|
| 115 | + /** @var \DirectoryIterator $data */ |
|
| 116 | + if ($data->isDir()) { |
|
| 117 | + continue; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + $relativeFileName = substr($filename, $baseDirectoryLength); |
|
| 121 | + $relativeFileName = ltrim($relativeFileName, '/'); |
|
| 122 | + |
|
| 123 | + // Exclude signature.json files in the appinfo and root folder |
|
| 124 | + if ($relativeFileName === 'appinfo/signature.json') { |
|
| 125 | + continue; |
|
| 126 | + } |
|
| 127 | + // Exclude signature.json files in the appinfo and core folder |
|
| 128 | + if ($relativeFileName === 'core/signature.json') { |
|
| 129 | + continue; |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + // The .htaccess file in the root folder of ownCloud can contain |
|
| 133 | + // custom content after the installation due to the fact that dynamic |
|
| 134 | + // content is written into it at installation time as well. This |
|
| 135 | + // includes for example the 404 and 403 instructions. |
|
| 136 | + // Thus we ignore everything below the first occurrence of |
|
| 137 | + // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the |
|
| 138 | + // hash generated based on this. |
|
| 139 | + if ($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { |
|
| 140 | + $fileContent = file_get_contents($filename); |
|
| 141 | + $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); |
|
| 142 | + if (\count($explodedArray) === 2) { |
|
| 143 | + $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); |
|
| 144 | + continue; |
|
| 145 | + } |
|
| 146 | + } |
|
| 147 | + if ($filename === $this->environmentHelper->getServerRoot() . '/core/js/mimetypelist.js') { |
|
| 148 | + $oldMimetypeList = new GenerateMimetypeFileBuilder(); |
|
| 149 | + $newFile = $oldMimetypeList->generateFile($this->mimeTypeDetector->getAllAliases(), $this->mimeTypeDetector->getAllNamings()); |
|
| 150 | + $oldFile = $this->fileAccessHelper->file_get_contents($filename); |
|
| 151 | + if ($newFile === $oldFile) { |
|
| 152 | + $hashes[$relativeFileName] = hash('sha512', $oldMimetypeList->generateFile($this->mimeTypeDetector->getOnlyDefaultAliases(), $this->mimeTypeDetector->getAllNamings())); |
|
| 153 | + continue; |
|
| 154 | + } |
|
| 155 | + } |
|
| 156 | + |
|
| 157 | + $hashes[$relativeFileName] = hash_file('sha512', $filename); |
|
| 158 | + } |
|
| 159 | + |
|
| 160 | + return $hashes; |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + /** |
|
| 164 | + * Creates the signature data |
|
| 165 | + * |
|
| 166 | + * @param array $hashes |
|
| 167 | + * @param X509 $certificate |
|
| 168 | + * @param RSA $privateKey |
|
| 169 | + * @return array |
|
| 170 | + */ |
|
| 171 | + private function createSignatureData(array $hashes, |
|
| 172 | + X509 $certificate, |
|
| 173 | + RSA $privateKey): array { |
|
| 174 | + ksort($hashes); |
|
| 175 | + |
|
| 176 | + $privateKey->setSignatureMode(RSA::SIGNATURE_PSS); |
|
| 177 | + $privateKey->setMGFHash('sha512'); |
|
| 178 | + // See https://tools.ietf.org/html/rfc3447#page-38 |
|
| 179 | + $privateKey->setSaltLength(0); |
|
| 180 | + $signature = $privateKey->sign(json_encode($hashes)); |
|
| 181 | + |
|
| 182 | + return [ |
|
| 183 | + 'hashes' => $hashes, |
|
| 184 | + 'signature' => base64_encode($signature), |
|
| 185 | + 'certificate' => $certificate->saveX509($certificate->currentCert), |
|
| 186 | + ]; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + /** |
|
| 190 | + * Write the signature of the app in the specified folder |
|
| 191 | + * |
|
| 192 | + * @param string $path |
|
| 193 | + * @param X509 $certificate |
|
| 194 | + * @param RSA $privateKey |
|
| 195 | + * @throws \Exception |
|
| 196 | + */ |
|
| 197 | + public function writeAppSignature($path, |
|
| 198 | + X509 $certificate, |
|
| 199 | + RSA $privateKey) { |
|
| 200 | + $appInfoDir = $path . '/appinfo'; |
|
| 201 | + try { |
|
| 202 | + $this->fileAccessHelper->assertDirectoryExists($appInfoDir); |
|
| 203 | + |
|
| 204 | + $iterator = $this->getFolderIterator($path); |
|
| 205 | + $hashes = $this->generateHashes($iterator, $path); |
|
| 206 | + $signature = $this->createSignatureData($hashes, $certificate, $privateKey); |
|
| 207 | + $this->fileAccessHelper->file_put_contents( |
|
| 208 | + $appInfoDir . '/signature.json', |
|
| 209 | + json_encode($signature, JSON_PRETTY_PRINT) |
|
| 210 | + ); |
|
| 211 | + } catch (\Exception $e) { |
|
| 212 | + if (!$this->fileAccessHelper->is_writable($appInfoDir)) { |
|
| 213 | + throw new \Exception($appInfoDir . ' is not writable'); |
|
| 214 | + } |
|
| 215 | + throw $e; |
|
| 216 | + } |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + /** |
|
| 220 | + * Write the signature of core |
|
| 221 | + * |
|
| 222 | + * @param X509 $certificate |
|
| 223 | + * @param RSA $rsa |
|
| 224 | + * @param string $path |
|
| 225 | + * @throws \Exception |
|
| 226 | + */ |
|
| 227 | + public function writeCoreSignature(X509 $certificate, |
|
| 228 | + RSA $rsa, |
|
| 229 | + $path) { |
|
| 230 | + $coreDir = $path . '/core'; |
|
| 231 | + try { |
|
| 232 | + $this->fileAccessHelper->assertDirectoryExists($coreDir); |
|
| 233 | + $iterator = $this->getFolderIterator($path, $path); |
|
| 234 | + $hashes = $this->generateHashes($iterator, $path); |
|
| 235 | + $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); |
|
| 236 | + $this->fileAccessHelper->file_put_contents( |
|
| 237 | + $coreDir . '/signature.json', |
|
| 238 | + json_encode($signatureData, JSON_PRETTY_PRINT) |
|
| 239 | + ); |
|
| 240 | + } catch (\Exception $e) { |
|
| 241 | + if (!$this->fileAccessHelper->is_writable($coreDir)) { |
|
| 242 | + throw new \Exception($coreDir . ' is not writable'); |
|
| 243 | + } |
|
| 244 | + throw $e; |
|
| 245 | + } |
|
| 246 | + } |
|
| 247 | + |
|
| 248 | + /** |
|
| 249 | + * Split the certificate file in individual certs |
|
| 250 | + * |
|
| 251 | + * @param string $cert |
|
| 252 | + * @return string[] |
|
| 253 | + */ |
|
| 254 | + private function splitCerts(string $cert): array { |
|
| 255 | + preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches); |
|
| 256 | + |
|
| 257 | + return $matches[0]; |
|
| 258 | + } |
|
| 259 | + |
|
| 260 | + /** |
|
| 261 | + * Verifies the signature for the specified path. |
|
| 262 | + * |
|
| 263 | + * @param string $signaturePath |
|
| 264 | + * @param string $basePath |
|
| 265 | + * @param string $certificateCN |
|
| 266 | + * @param bool $forceVerify |
|
| 267 | + * @return array |
|
| 268 | + * @throws InvalidSignatureException |
|
| 269 | + * @throws \Exception |
|
| 270 | + */ |
|
| 271 | + private function verify(string $signaturePath, string $basePath, string $certificateCN, bool $forceVerify = false): array { |
|
| 272 | + if (!$forceVerify && !$this->isCodeCheckEnforced()) { |
|
| 273 | + return []; |
|
| 274 | + } |
|
| 275 | + |
|
| 276 | + $content = $this->fileAccessHelper->file_get_contents($signaturePath); |
|
| 277 | + $signatureData = null; |
|
| 278 | + |
|
| 279 | + if (\is_string($content)) { |
|
| 280 | + $signatureData = json_decode($content, true); |
|
| 281 | + } |
|
| 282 | + if (!\is_array($signatureData)) { |
|
| 283 | + throw new InvalidSignatureException('Signature data not found.'); |
|
| 284 | + } |
|
| 285 | + |
|
| 286 | + $expectedHashes = $signatureData['hashes']; |
|
| 287 | + ksort($expectedHashes); |
|
| 288 | + $signature = base64_decode($signatureData['signature']); |
|
| 289 | + $certificate = $signatureData['certificate']; |
|
| 290 | + |
|
| 291 | + // Check if certificate is signed by Nextcloud Root Authority |
|
| 292 | + $x509 = new \phpseclib\File\X509(); |
|
| 293 | + $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot() . '/resources/codesigning/root.crt'); |
|
| 294 | + |
|
| 295 | + $rootCerts = $this->splitCerts($rootCertificatePublicKey); |
|
| 296 | + foreach ($rootCerts as $rootCert) { |
|
| 297 | + $x509->loadCA($rootCert); |
|
| 298 | + } |
|
| 299 | + $x509->loadX509($certificate); |
|
| 300 | + if (!$x509->validateSignature()) { |
|
| 301 | + throw new InvalidSignatureException('Certificate is not valid.'); |
|
| 302 | + } |
|
| 303 | + // Verify if certificate has proper CN. "core" CN is always trusted. |
|
| 304 | + if ($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') { |
|
| 305 | + throw new InvalidSignatureException( |
|
| 306 | + sprintf('Certificate is not valid for required scope. (Requested: %s, current: CN=%s)', $certificateCN, $x509->getDN(true)['CN']) |
|
| 307 | + ); |
|
| 308 | + } |
|
| 309 | + |
|
| 310 | + // Check if the signature of the files is valid |
|
| 311 | + $rsa = new \phpseclib\Crypt\RSA(); |
|
| 312 | + $rsa->loadKey($x509->currentCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']); |
|
| 313 | + $rsa->setSignatureMode(RSA::SIGNATURE_PSS); |
|
| 314 | + $rsa->setMGFHash('sha512'); |
|
| 315 | + // See https://tools.ietf.org/html/rfc3447#page-38 |
|
| 316 | + $rsa->setSaltLength(0); |
|
| 317 | + if (!$rsa->verify(json_encode($expectedHashes), $signature)) { |
|
| 318 | + throw new InvalidSignatureException('Signature could not get verified.'); |
|
| 319 | + } |
|
| 320 | + |
|
| 321 | + // Fixes for the updater as shipped with ownCloud 9.0.x: The updater is |
|
| 322 | + // replaced after the code integrity check is performed. |
|
| 323 | + // |
|
| 324 | + // Due to this reason we exclude the whole updater/ folder from the code |
|
| 325 | + // integrity check. |
|
| 326 | + if ($basePath === $this->environmentHelper->getServerRoot()) { |
|
| 327 | + foreach ($expectedHashes as $fileName => $hash) { |
|
| 328 | + if (str_starts_with($fileName, 'updater/')) { |
|
| 329 | + unset($expectedHashes[$fileName]); |
|
| 330 | + } |
|
| 331 | + } |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + // Compare the list of files which are not identical |
|
| 335 | + $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($basePath), $basePath); |
|
| 336 | + $differencesA = array_diff_assoc($expectedHashes, $currentInstanceHashes); |
|
| 337 | + $differencesB = array_diff_assoc($currentInstanceHashes, $expectedHashes); |
|
| 338 | + $differences = array_unique(array_merge($differencesA, $differencesB)); |
|
| 339 | + $differenceArray = []; |
|
| 340 | + foreach ($differences as $filename => $hash) { |
|
| 341 | + // Check if file should not exist in the new signature table |
|
| 342 | + if (!array_key_exists($filename, $expectedHashes)) { |
|
| 343 | + $differenceArray['EXTRA_FILE'][$filename]['expected'] = ''; |
|
| 344 | + $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash; |
|
| 345 | + continue; |
|
| 346 | + } |
|
| 347 | + |
|
| 348 | + // Check if file is missing |
|
| 349 | + if (!array_key_exists($filename, $currentInstanceHashes)) { |
|
| 350 | + $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes[$filename]; |
|
| 351 | + $differenceArray['FILE_MISSING'][$filename]['current'] = ''; |
|
| 352 | + continue; |
|
| 353 | + } |
|
| 354 | + |
|
| 355 | + // Check if hash does mismatch |
|
| 356 | + if ($expectedHashes[$filename] !== $currentInstanceHashes[$filename]) { |
|
| 357 | + $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes[$filename]; |
|
| 358 | + $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename]; |
|
| 359 | + continue; |
|
| 360 | + } |
|
| 361 | + |
|
| 362 | + // Should never happen. |
|
| 363 | + throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.'); |
|
| 364 | + } |
|
| 365 | + |
|
| 366 | + return $differenceArray; |
|
| 367 | + } |
|
| 368 | + |
|
| 369 | + /** |
|
| 370 | + * Whether the code integrity check has passed successful or not |
|
| 371 | + * |
|
| 372 | + * @return bool |
|
| 373 | + */ |
|
| 374 | + public function hasPassedCheck(): bool { |
|
| 375 | + $results = $this->getResults(); |
|
| 376 | + if ($results !== null && empty($results)) { |
|
| 377 | + return true; |
|
| 378 | + } |
|
| 379 | + |
|
| 380 | + return false; |
|
| 381 | + } |
|
| 382 | + |
|
| 383 | + /** |
|
| 384 | + * @return array|null Either the results or null if no results available |
|
| 385 | + */ |
|
| 386 | + public function getResults(): ?array { |
|
| 387 | + $cachedResults = $this->cache->get(self::CACHE_KEY); |
|
| 388 | + if (!\is_null($cachedResults) && $cachedResults !== false) { |
|
| 389 | + return json_decode($cachedResults, true); |
|
| 390 | + } |
|
| 391 | + |
|
| 392 | + if ($this->appConfig?->hasKey('core', self::CACHE_KEY, lazy: true)) { |
|
| 393 | + return $this->appConfig->getValueArray('core', self::CACHE_KEY, lazy: true); |
|
| 394 | + } |
|
| 395 | + |
|
| 396 | + // No results available |
|
| 397 | + return null; |
|
| 398 | + } |
|
| 399 | + |
|
| 400 | + /** |
|
| 401 | + * Stores the results in the app config as well as cache |
|
| 402 | + * |
|
| 403 | + * @param string $scope |
|
| 404 | + * @param array $result |
|
| 405 | + */ |
|
| 406 | + private function storeResults(string $scope, array $result) { |
|
| 407 | + $resultArray = $this->getResults() ?? []; |
|
| 408 | + unset($resultArray[$scope]); |
|
| 409 | + if (!empty($result)) { |
|
| 410 | + $resultArray[$scope] = $result; |
|
| 411 | + } |
|
| 412 | + $this->appConfig?->setValueArray('core', self::CACHE_KEY, $resultArray, lazy: true); |
|
| 413 | + $this->cache->set(self::CACHE_KEY, json_encode($resultArray)); |
|
| 414 | + } |
|
| 415 | + |
|
| 416 | + /** |
|
| 417 | + * |
|
| 418 | + * Clean previous results for a proper rescanning. Otherwise |
|
| 419 | + */ |
|
| 420 | + private function cleanResults() { |
|
| 421 | + $this->appConfig->deleteKey('core', self::CACHE_KEY); |
|
| 422 | + $this->cache->remove(self::CACHE_KEY); |
|
| 423 | + } |
|
| 424 | + |
|
| 425 | + /** |
|
| 426 | + * Verify the signature of $appId. Returns an array with the following content: |
|
| 427 | + * [ |
|
| 428 | + * 'FILE_MISSING' => |
|
| 429 | + * [ |
|
| 430 | + * 'filename' => [ |
|
| 431 | + * 'expected' => 'expectedSHA512', |
|
| 432 | + * 'current' => 'currentSHA512', |
|
| 433 | + * ], |
|
| 434 | + * ], |
|
| 435 | + * 'EXTRA_FILE' => |
|
| 436 | + * [ |
|
| 437 | + * 'filename' => [ |
|
| 438 | + * 'expected' => 'expectedSHA512', |
|
| 439 | + * 'current' => 'currentSHA512', |
|
| 440 | + * ], |
|
| 441 | + * ], |
|
| 442 | + * 'INVALID_HASH' => |
|
| 443 | + * [ |
|
| 444 | + * 'filename' => [ |
|
| 445 | + * 'expected' => 'expectedSHA512', |
|
| 446 | + * 'current' => 'currentSHA512', |
|
| 447 | + * ], |
|
| 448 | + * ], |
|
| 449 | + * ] |
|
| 450 | + * |
|
| 451 | + * Array may be empty in case no problems have been found. |
|
| 452 | + * |
|
| 453 | + * @param string $appId |
|
| 454 | + * @param string $path Optional path. If none is given it will be guessed. |
|
| 455 | + * @param bool $forceVerify |
|
| 456 | + * @return array |
|
| 457 | + */ |
|
| 458 | + public function verifyAppSignature(string $appId, string $path = '', bool $forceVerify = false): array { |
|
| 459 | + try { |
|
| 460 | + if ($path === '') { |
|
| 461 | + $path = $this->appManager->getAppPath($appId); |
|
| 462 | + } |
|
| 463 | + $result = $this->verify( |
|
| 464 | + $path . '/appinfo/signature.json', |
|
| 465 | + $path, |
|
| 466 | + $appId, |
|
| 467 | + $forceVerify |
|
| 468 | + ); |
|
| 469 | + } catch (\Exception $e) { |
|
| 470 | + $result = [ |
|
| 471 | + 'EXCEPTION' => [ |
|
| 472 | + 'class' => \get_class($e), |
|
| 473 | + 'message' => $e->getMessage(), |
|
| 474 | + ], |
|
| 475 | + ]; |
|
| 476 | + } |
|
| 477 | + $this->storeResults($appId, $result); |
|
| 478 | + |
|
| 479 | + return $result; |
|
| 480 | + } |
|
| 481 | + |
|
| 482 | + /** |
|
| 483 | + * Verify the signature of core. Returns an array with the following content: |
|
| 484 | + * [ |
|
| 485 | + * 'FILE_MISSING' => |
|
| 486 | + * [ |
|
| 487 | + * 'filename' => [ |
|
| 488 | + * 'expected' => 'expectedSHA512', |
|
| 489 | + * 'current' => 'currentSHA512', |
|
| 490 | + * ], |
|
| 491 | + * ], |
|
| 492 | + * 'EXTRA_FILE' => |
|
| 493 | + * [ |
|
| 494 | + * 'filename' => [ |
|
| 495 | + * 'expected' => 'expectedSHA512', |
|
| 496 | + * 'current' => 'currentSHA512', |
|
| 497 | + * ], |
|
| 498 | + * ], |
|
| 499 | + * 'INVALID_HASH' => |
|
| 500 | + * [ |
|
| 501 | + * 'filename' => [ |
|
| 502 | + * 'expected' => 'expectedSHA512', |
|
| 503 | + * 'current' => 'currentSHA512', |
|
| 504 | + * ], |
|
| 505 | + * ], |
|
| 506 | + * ] |
|
| 507 | + * |
|
| 508 | + * Array may be empty in case no problems have been found. |
|
| 509 | + * |
|
| 510 | + * @return array |
|
| 511 | + */ |
|
| 512 | + public function verifyCoreSignature(): array { |
|
| 513 | + try { |
|
| 514 | + $result = $this->verify( |
|
| 515 | + $this->environmentHelper->getServerRoot() . '/core/signature.json', |
|
| 516 | + $this->environmentHelper->getServerRoot(), |
|
| 517 | + 'core' |
|
| 518 | + ); |
|
| 519 | + } catch (\Exception $e) { |
|
| 520 | + $result = [ |
|
| 521 | + 'EXCEPTION' => [ |
|
| 522 | + 'class' => \get_class($e), |
|
| 523 | + 'message' => $e->getMessage(), |
|
| 524 | + ], |
|
| 525 | + ]; |
|
| 526 | + } |
|
| 527 | + $this->storeResults('core', $result); |
|
| 528 | + |
|
| 529 | + return $result; |
|
| 530 | + } |
|
| 531 | + |
|
| 532 | + /** |
|
| 533 | + * Verify the core code of the instance as well as all applicable applications |
|
| 534 | + * and store the results. |
|
| 535 | + */ |
|
| 536 | + public function runInstanceVerification() { |
|
| 537 | + $this->cleanResults(); |
|
| 538 | + $this->verifyCoreSignature(); |
|
| 539 | + $appIds = $this->appManager->getAllAppsInAppsFolders(); |
|
| 540 | + foreach ($appIds as $appId) { |
|
| 541 | + // If an application is shipped a valid signature is required |
|
| 542 | + $isShipped = $this->appManager->isShipped($appId); |
|
| 543 | + $appNeedsToBeChecked = false; |
|
| 544 | + if ($isShipped) { |
|
| 545 | + $appNeedsToBeChecked = true; |
|
| 546 | + } elseif ($this->fileAccessHelper->file_exists($this->appManager->getAppPath($appId) . '/appinfo/signature.json')) { |
|
| 547 | + // Otherwise only if the application explicitly ships a signature.json file |
|
| 548 | + $appNeedsToBeChecked = true; |
|
| 549 | + } |
|
| 550 | + |
|
| 551 | + if ($appNeedsToBeChecked) { |
|
| 552 | + $this->verifyAppSignature($appId); |
|
| 553 | + } |
|
| 554 | + } |
|
| 555 | + } |
|
| 556 | 556 | } |
@@ -11,83 +11,83 @@ |
||
| 11 | 11 | * @deprecated 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service |
| 12 | 12 | */ |
| 13 | 13 | trait EmitterTrait { |
| 14 | - /** |
|
| 15 | - * @var callable[][] $listeners |
|
| 16 | - */ |
|
| 17 | - protected $listeners = []; |
|
| 14 | + /** |
|
| 15 | + * @var callable[][] $listeners |
|
| 16 | + */ |
|
| 17 | + protected $listeners = []; |
|
| 18 | 18 | |
| 19 | - /** |
|
| 20 | - * @param string $scope |
|
| 21 | - * @param string $method |
|
| 22 | - * @param callable $callback |
|
| 23 | - * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener |
|
| 24 | - */ |
|
| 25 | - public function listen($scope, $method, callable $callback) { |
|
| 26 | - $eventName = $scope . '::' . $method; |
|
| 27 | - if (!isset($this->listeners[$eventName])) { |
|
| 28 | - $this->listeners[$eventName] = []; |
|
| 29 | - } |
|
| 30 | - if (!in_array($callback, $this->listeners[$eventName], true)) { |
|
| 31 | - $this->listeners[$eventName][] = $callback; |
|
| 32 | - } |
|
| 33 | - } |
|
| 19 | + /** |
|
| 20 | + * @param string $scope |
|
| 21 | + * @param string $method |
|
| 22 | + * @param callable $callback |
|
| 23 | + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener |
|
| 24 | + */ |
|
| 25 | + public function listen($scope, $method, callable $callback) { |
|
| 26 | + $eventName = $scope . '::' . $method; |
|
| 27 | + if (!isset($this->listeners[$eventName])) { |
|
| 28 | + $this->listeners[$eventName] = []; |
|
| 29 | + } |
|
| 30 | + if (!in_array($callback, $this->listeners[$eventName], true)) { |
|
| 31 | + $this->listeners[$eventName][] = $callback; |
|
| 32 | + } |
|
| 33 | + } |
|
| 34 | 34 | |
| 35 | - /** |
|
| 36 | - * @param string $scope optional |
|
| 37 | - * @param string $method optional |
|
| 38 | - * @param callable $callback optional |
|
| 39 | - * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::removeListener |
|
| 40 | - */ |
|
| 41 | - public function removeListener($scope = null, $method = null, ?callable $callback = null) { |
|
| 42 | - $names = []; |
|
| 43 | - $allNames = array_keys($this->listeners); |
|
| 44 | - if ($scope && $method) { |
|
| 45 | - $name = $scope . '::' . $method; |
|
| 46 | - if (isset($this->listeners[$name])) { |
|
| 47 | - $names[] = $name; |
|
| 48 | - } |
|
| 49 | - } elseif ($scope) { |
|
| 50 | - foreach ($allNames as $name) { |
|
| 51 | - $parts = explode('::', $name, 2); |
|
| 52 | - if ($parts[0] == $scope) { |
|
| 53 | - $names[] = $name; |
|
| 54 | - } |
|
| 55 | - } |
|
| 56 | - } elseif ($method) { |
|
| 57 | - foreach ($allNames as $name) { |
|
| 58 | - $parts = explode('::', $name, 2); |
|
| 59 | - if ($parts[1] == $method) { |
|
| 60 | - $names[] = $name; |
|
| 61 | - } |
|
| 62 | - } |
|
| 63 | - } else { |
|
| 64 | - $names = $allNames; |
|
| 65 | - } |
|
| 35 | + /** |
|
| 36 | + * @param string $scope optional |
|
| 37 | + * @param string $method optional |
|
| 38 | + * @param callable $callback optional |
|
| 39 | + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::removeListener |
|
| 40 | + */ |
|
| 41 | + public function removeListener($scope = null, $method = null, ?callable $callback = null) { |
|
| 42 | + $names = []; |
|
| 43 | + $allNames = array_keys($this->listeners); |
|
| 44 | + if ($scope && $method) { |
|
| 45 | + $name = $scope . '::' . $method; |
|
| 46 | + if (isset($this->listeners[$name])) { |
|
| 47 | + $names[] = $name; |
|
| 48 | + } |
|
| 49 | + } elseif ($scope) { |
|
| 50 | + foreach ($allNames as $name) { |
|
| 51 | + $parts = explode('::', $name, 2); |
|
| 52 | + if ($parts[0] == $scope) { |
|
| 53 | + $names[] = $name; |
|
| 54 | + } |
|
| 55 | + } |
|
| 56 | + } elseif ($method) { |
|
| 57 | + foreach ($allNames as $name) { |
|
| 58 | + $parts = explode('::', $name, 2); |
|
| 59 | + if ($parts[1] == $method) { |
|
| 60 | + $names[] = $name; |
|
| 61 | + } |
|
| 62 | + } |
|
| 63 | + } else { |
|
| 64 | + $names = $allNames; |
|
| 65 | + } |
|
| 66 | 66 | |
| 67 | - foreach ($names as $name) { |
|
| 68 | - if ($callback) { |
|
| 69 | - $index = array_search($callback, $this->listeners[$name], true); |
|
| 70 | - if ($index !== false) { |
|
| 71 | - unset($this->listeners[$name][$index]); |
|
| 72 | - } |
|
| 73 | - } else { |
|
| 74 | - $this->listeners[$name] = []; |
|
| 75 | - } |
|
| 76 | - } |
|
| 77 | - } |
|
| 67 | + foreach ($names as $name) { |
|
| 68 | + if ($callback) { |
|
| 69 | + $index = array_search($callback, $this->listeners[$name], true); |
|
| 70 | + if ($index !== false) { |
|
| 71 | + unset($this->listeners[$name][$index]); |
|
| 72 | + } |
|
| 73 | + } else { |
|
| 74 | + $this->listeners[$name] = []; |
|
| 75 | + } |
|
| 76 | + } |
|
| 77 | + } |
|
| 78 | 78 | |
| 79 | - /** |
|
| 80 | - * @param string $scope |
|
| 81 | - * @param string $method |
|
| 82 | - * @param array $arguments optional |
|
| 83 | - * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTyped |
|
| 84 | - */ |
|
| 85 | - protected function emit($scope, $method, array $arguments = []) { |
|
| 86 | - $eventName = $scope . '::' . $method; |
|
| 87 | - if (isset($this->listeners[$eventName])) { |
|
| 88 | - foreach ($this->listeners[$eventName] as $callback) { |
|
| 89 | - call_user_func_array($callback, $arguments); |
|
| 90 | - } |
|
| 91 | - } |
|
| 92 | - } |
|
| 79 | + /** |
|
| 80 | + * @param string $scope |
|
| 81 | + * @param string $method |
|
| 82 | + * @param array $arguments optional |
|
| 83 | + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTyped |
|
| 84 | + */ |
|
| 85 | + protected function emit($scope, $method, array $arguments = []) { |
|
| 86 | + $eventName = $scope . '::' . $method; |
|
| 87 | + if (isset($this->listeners[$eventName])) { |
|
| 88 | + foreach ($this->listeners[$eventName] as $callback) { |
|
| 89 | + call_user_func_array($callback, $arguments); |
|
| 90 | + } |
|
| 91 | + } |
|
| 92 | + } |
|
| 93 | 93 | } |
@@ -23,7 +23,7 @@ discard block |
||
| 23 | 23 | * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener |
| 24 | 24 | */ |
| 25 | 25 | public function listen($scope, $method, callable $callback) { |
| 26 | - $eventName = $scope . '::' . $method; |
|
| 26 | + $eventName = $scope.'::'.$method; |
|
| 27 | 27 | if (!isset($this->listeners[$eventName])) { |
| 28 | 28 | $this->listeners[$eventName] = []; |
| 29 | 29 | } |
@@ -42,7 +42,7 @@ discard block |
||
| 42 | 42 | $names = []; |
| 43 | 43 | $allNames = array_keys($this->listeners); |
| 44 | 44 | if ($scope && $method) { |
| 45 | - $name = $scope . '::' . $method; |
|
| 45 | + $name = $scope.'::'.$method; |
|
| 46 | 46 | if (isset($this->listeners[$name])) { |
| 47 | 47 | $names[] = $name; |
| 48 | 48 | } |
@@ -83,7 +83,7 @@ discard block |
||
| 83 | 83 | * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTyped |
| 84 | 84 | */ |
| 85 | 85 | protected function emit($scope, $method, array $arguments = []) { |
| 86 | - $eventName = $scope . '::' . $method; |
|
| 86 | + $eventName = $scope.'::'.$method; |
|
| 87 | 87 | if (isset($this->listeners[$eventName])) { |
| 88 | 88 | foreach ($this->listeners[$eventName] as $callback) { |
| 89 | 89 | call_user_func_array($callback, $arguments); |
@@ -11,114 +11,114 @@ |
||
| 11 | 11 | use OCP\IMemcache; |
| 12 | 12 | |
| 13 | 13 | class APCu extends Cache implements IMemcache { |
| 14 | - use CASTrait { |
|
| 15 | - cas as casEmulated; |
|
| 16 | - } |
|
| 14 | + use CASTrait { |
|
| 15 | + cas as casEmulated; |
|
| 16 | + } |
|
| 17 | 17 | |
| 18 | - use CADTrait; |
|
| 18 | + use CADTrait; |
|
| 19 | 19 | |
| 20 | - public function get($key) { |
|
| 21 | - $result = apcu_fetch($this->getPrefix() . $key, $success); |
|
| 22 | - if (!$success) { |
|
| 23 | - return null; |
|
| 24 | - } |
|
| 25 | - return $result; |
|
| 26 | - } |
|
| 20 | + public function get($key) { |
|
| 21 | + $result = apcu_fetch($this->getPrefix() . $key, $success); |
|
| 22 | + if (!$success) { |
|
| 23 | + return null; |
|
| 24 | + } |
|
| 25 | + return $result; |
|
| 26 | + } |
|
| 27 | 27 | |
| 28 | - public function set($key, $value, $ttl = 0) { |
|
| 29 | - if ($ttl === 0) { |
|
| 30 | - $ttl = self::DEFAULT_TTL; |
|
| 31 | - } |
|
| 32 | - return apcu_store($this->getPrefix() . $key, $value, $ttl); |
|
| 33 | - } |
|
| 28 | + public function set($key, $value, $ttl = 0) { |
|
| 29 | + if ($ttl === 0) { |
|
| 30 | + $ttl = self::DEFAULT_TTL; |
|
| 31 | + } |
|
| 32 | + return apcu_store($this->getPrefix() . $key, $value, $ttl); |
|
| 33 | + } |
|
| 34 | 34 | |
| 35 | - public function hasKey($key) { |
|
| 36 | - return apcu_exists($this->getPrefix() . $key); |
|
| 37 | - } |
|
| 35 | + public function hasKey($key) { |
|
| 36 | + return apcu_exists($this->getPrefix() . $key); |
|
| 37 | + } |
|
| 38 | 38 | |
| 39 | - public function remove($key) { |
|
| 40 | - return apcu_delete($this->getPrefix() . $key); |
|
| 41 | - } |
|
| 39 | + public function remove($key) { |
|
| 40 | + return apcu_delete($this->getPrefix() . $key); |
|
| 41 | + } |
|
| 42 | 42 | |
| 43 | - public function clear($prefix = '') { |
|
| 44 | - $ns = $this->getPrefix() . $prefix; |
|
| 45 | - $ns = preg_quote($ns, '/'); |
|
| 46 | - if (class_exists('\APCIterator')) { |
|
| 47 | - $iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY); |
|
| 48 | - } else { |
|
| 49 | - $iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); |
|
| 50 | - } |
|
| 51 | - return apcu_delete($iter); |
|
| 52 | - } |
|
| 43 | + public function clear($prefix = '') { |
|
| 44 | + $ns = $this->getPrefix() . $prefix; |
|
| 45 | + $ns = preg_quote($ns, '/'); |
|
| 46 | + if (class_exists('\APCIterator')) { |
|
| 47 | + $iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY); |
|
| 48 | + } else { |
|
| 49 | + $iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); |
|
| 50 | + } |
|
| 51 | + return apcu_delete($iter); |
|
| 52 | + } |
|
| 53 | 53 | |
| 54 | - /** |
|
| 55 | - * Set a value in the cache if it's not already stored |
|
| 56 | - * |
|
| 57 | - * @param string $key |
|
| 58 | - * @param mixed $value |
|
| 59 | - * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 |
|
| 60 | - * @return bool |
|
| 61 | - */ |
|
| 62 | - public function add($key, $value, $ttl = 0) { |
|
| 63 | - if ($ttl === 0) { |
|
| 64 | - $ttl = self::DEFAULT_TTL; |
|
| 65 | - } |
|
| 66 | - return apcu_add($this->getPrefix() . $key, $value, $ttl); |
|
| 67 | - } |
|
| 54 | + /** |
|
| 55 | + * Set a value in the cache if it's not already stored |
|
| 56 | + * |
|
| 57 | + * @param string $key |
|
| 58 | + * @param mixed $value |
|
| 59 | + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 |
|
| 60 | + * @return bool |
|
| 61 | + */ |
|
| 62 | + public function add($key, $value, $ttl = 0) { |
|
| 63 | + if ($ttl === 0) { |
|
| 64 | + $ttl = self::DEFAULT_TTL; |
|
| 65 | + } |
|
| 66 | + return apcu_add($this->getPrefix() . $key, $value, $ttl); |
|
| 67 | + } |
|
| 68 | 68 | |
| 69 | - /** |
|
| 70 | - * Increase a stored number |
|
| 71 | - * |
|
| 72 | - * @param string $key |
|
| 73 | - * @param int $step |
|
| 74 | - * @return int | bool |
|
| 75 | - */ |
|
| 76 | - public function inc($key, $step = 1) { |
|
| 77 | - $success = null; |
|
| 78 | - return apcu_inc($this->getPrefix() . $key, $step, $success, self::DEFAULT_TTL); |
|
| 79 | - } |
|
| 69 | + /** |
|
| 70 | + * Increase a stored number |
|
| 71 | + * |
|
| 72 | + * @param string $key |
|
| 73 | + * @param int $step |
|
| 74 | + * @return int | bool |
|
| 75 | + */ |
|
| 76 | + public function inc($key, $step = 1) { |
|
| 77 | + $success = null; |
|
| 78 | + return apcu_inc($this->getPrefix() . $key, $step, $success, self::DEFAULT_TTL); |
|
| 79 | + } |
|
| 80 | 80 | |
| 81 | - /** |
|
| 82 | - * Decrease a stored number |
|
| 83 | - * |
|
| 84 | - * @param string $key |
|
| 85 | - * @param int $step |
|
| 86 | - * @return int | bool |
|
| 87 | - */ |
|
| 88 | - public function dec($key, $step = 1) { |
|
| 89 | - return apcu_exists($this->getPrefix() . $key) |
|
| 90 | - ? apcu_dec($this->getPrefix() . $key, $step) |
|
| 91 | - : false; |
|
| 92 | - } |
|
| 81 | + /** |
|
| 82 | + * Decrease a stored number |
|
| 83 | + * |
|
| 84 | + * @param string $key |
|
| 85 | + * @param int $step |
|
| 86 | + * @return int | bool |
|
| 87 | + */ |
|
| 88 | + public function dec($key, $step = 1) { |
|
| 89 | + return apcu_exists($this->getPrefix() . $key) |
|
| 90 | + ? apcu_dec($this->getPrefix() . $key, $step) |
|
| 91 | + : false; |
|
| 92 | + } |
|
| 93 | 93 | |
| 94 | - /** |
|
| 95 | - * Compare and set |
|
| 96 | - * |
|
| 97 | - * @param string $key |
|
| 98 | - * @param mixed $old |
|
| 99 | - * @param mixed $new |
|
| 100 | - * @return bool |
|
| 101 | - */ |
|
| 102 | - public function cas($key, $old, $new) { |
|
| 103 | - // apc only does cas for ints |
|
| 104 | - if (is_int($old) && is_int($new)) { |
|
| 105 | - return apcu_cas($this->getPrefix() . $key, $old, $new); |
|
| 106 | - } else { |
|
| 107 | - return $this->casEmulated($key, $old, $new); |
|
| 108 | - } |
|
| 109 | - } |
|
| 94 | + /** |
|
| 95 | + * Compare and set |
|
| 96 | + * |
|
| 97 | + * @param string $key |
|
| 98 | + * @param mixed $old |
|
| 99 | + * @param mixed $new |
|
| 100 | + * @return bool |
|
| 101 | + */ |
|
| 102 | + public function cas($key, $old, $new) { |
|
| 103 | + // apc only does cas for ints |
|
| 104 | + if (is_int($old) && is_int($new)) { |
|
| 105 | + return apcu_cas($this->getPrefix() . $key, $old, $new); |
|
| 106 | + } else { |
|
| 107 | + return $this->casEmulated($key, $old, $new); |
|
| 108 | + } |
|
| 109 | + } |
|
| 110 | 110 | |
| 111 | - public static function isAvailable(): bool { |
|
| 112 | - if (!extension_loaded('apcu')) { |
|
| 113 | - return false; |
|
| 114 | - } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { |
|
| 115 | - return false; |
|
| 116 | - } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) { |
|
| 117 | - return false; |
|
| 118 | - } elseif (version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1) { |
|
| 119 | - return false; |
|
| 120 | - } else { |
|
| 121 | - return true; |
|
| 122 | - } |
|
| 123 | - } |
|
| 111 | + public static function isAvailable(): bool { |
|
| 112 | + if (!extension_loaded('apcu')) { |
|
| 113 | + return false; |
|
| 114 | + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { |
|
| 115 | + return false; |
|
| 116 | + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) { |
|
| 117 | + return false; |
|
| 118 | + } elseif (version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1) { |
|
| 119 | + return false; |
|
| 120 | + } else { |
|
| 121 | + return true; |
|
| 122 | + } |
|
| 123 | + } |
|
| 124 | 124 | } |
@@ -18,7 +18,7 @@ discard block |
||
| 18 | 18 | use CADTrait; |
| 19 | 19 | |
| 20 | 20 | public function get($key) { |
| 21 | - $result = apcu_fetch($this->getPrefix() . $key, $success); |
|
| 21 | + $result = apcu_fetch($this->getPrefix().$key, $success); |
|
| 22 | 22 | if (!$success) { |
| 23 | 23 | return null; |
| 24 | 24 | } |
@@ -29,24 +29,24 @@ discard block |
||
| 29 | 29 | if ($ttl === 0) { |
| 30 | 30 | $ttl = self::DEFAULT_TTL; |
| 31 | 31 | } |
| 32 | - return apcu_store($this->getPrefix() . $key, $value, $ttl); |
|
| 32 | + return apcu_store($this->getPrefix().$key, $value, $ttl); |
|
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | public function hasKey($key) { |
| 36 | - return apcu_exists($this->getPrefix() . $key); |
|
| 36 | + return apcu_exists($this->getPrefix().$key); |
|
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | public function remove($key) { |
| 40 | - return apcu_delete($this->getPrefix() . $key); |
|
| 40 | + return apcu_delete($this->getPrefix().$key); |
|
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | public function clear($prefix = '') { |
| 44 | - $ns = $this->getPrefix() . $prefix; |
|
| 44 | + $ns = $this->getPrefix().$prefix; |
|
| 45 | 45 | $ns = preg_quote($ns, '/'); |
| 46 | 46 | if (class_exists('\APCIterator')) { |
| 47 | - $iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY); |
|
| 47 | + $iter = new \APCIterator('user', '/^'.$ns.'/', APC_ITER_KEY); |
|
| 48 | 48 | } else { |
| 49 | - $iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); |
|
| 49 | + $iter = new \APCUIterator('/^'.$ns.'/', APC_ITER_KEY); |
|
| 50 | 50 | } |
| 51 | 51 | return apcu_delete($iter); |
| 52 | 52 | } |
@@ -63,7 +63,7 @@ discard block |
||
| 63 | 63 | if ($ttl === 0) { |
| 64 | 64 | $ttl = self::DEFAULT_TTL; |
| 65 | 65 | } |
| 66 | - return apcu_add($this->getPrefix() . $key, $value, $ttl); |
|
| 66 | + return apcu_add($this->getPrefix().$key, $value, $ttl); |
|
| 67 | 67 | } |
| 68 | 68 | |
| 69 | 69 | /** |
@@ -75,7 +75,7 @@ discard block |
||
| 75 | 75 | */ |
| 76 | 76 | public function inc($key, $step = 1) { |
| 77 | 77 | $success = null; |
| 78 | - return apcu_inc($this->getPrefix() . $key, $step, $success, self::DEFAULT_TTL); |
|
| 78 | + return apcu_inc($this->getPrefix().$key, $step, $success, self::DEFAULT_TTL); |
|
| 79 | 79 | } |
| 80 | 80 | |
| 81 | 81 | /** |
@@ -86,8 +86,8 @@ discard block |
||
| 86 | 86 | * @return int | bool |
| 87 | 87 | */ |
| 88 | 88 | public function dec($key, $step = 1) { |
| 89 | - return apcu_exists($this->getPrefix() . $key) |
|
| 90 | - ? apcu_dec($this->getPrefix() . $key, $step) |
|
| 89 | + return apcu_exists($this->getPrefix().$key) |
|
| 90 | + ? apcu_dec($this->getPrefix().$key, $step) |
|
| 91 | 91 | : false; |
| 92 | 92 | } |
| 93 | 93 | |
@@ -102,7 +102,7 @@ discard block |
||
| 102 | 102 | public function cas($key, $old, $new) { |
| 103 | 103 | // apc only does cas for ints |
| 104 | 104 | if (is_int($old) && is_int($new)) { |
| 105 | - return apcu_cas($this->getPrefix() . $key, $old, $new); |
|
| 105 | + return apcu_cas($this->getPrefix().$key, $old, $new); |
|
| 106 | 106 | } else { |
| 107 | 107 | return $this->casEmulated($key, $old, $new); |
| 108 | 108 | } |
@@ -11,162 +11,162 @@ |
||
| 11 | 11 | use OCP\IMemcache; |
| 12 | 12 | |
| 13 | 13 | class Memcached extends Cache implements IMemcache { |
| 14 | - use CASTrait; |
|
| 15 | - |
|
| 16 | - /** |
|
| 17 | - * @var \Memcached $cache |
|
| 18 | - */ |
|
| 19 | - private static $cache = null; |
|
| 20 | - |
|
| 21 | - use CADTrait; |
|
| 22 | - |
|
| 23 | - public function __construct($prefix = '') { |
|
| 24 | - parent::__construct($prefix); |
|
| 25 | - if (is_null(self::$cache)) { |
|
| 26 | - self::$cache = new \Memcached(); |
|
| 27 | - |
|
| 28 | - $defaultOptions = [ |
|
| 29 | - \Memcached::OPT_CONNECT_TIMEOUT => 50, |
|
| 30 | - \Memcached::OPT_RETRY_TIMEOUT => 50, |
|
| 31 | - \Memcached::OPT_SEND_TIMEOUT => 50, |
|
| 32 | - \Memcached::OPT_RECV_TIMEOUT => 50, |
|
| 33 | - \Memcached::OPT_POLL_TIMEOUT => 50, |
|
| 34 | - |
|
| 35 | - // Enable compression |
|
| 36 | - \Memcached::OPT_COMPRESSION => true, |
|
| 37 | - |
|
| 38 | - // Turn on consistent hashing |
|
| 39 | - \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, |
|
| 40 | - |
|
| 41 | - // Enable Binary Protocol |
|
| 42 | - \Memcached::OPT_BINARY_PROTOCOL => true, |
|
| 43 | - ]; |
|
| 44 | - /** |
|
| 45 | - * By default enable igbinary serializer if available |
|
| 46 | - * |
|
| 47 | - * Psalm checks depend on if igbinary is installed or not with memcached |
|
| 48 | - * @psalm-suppress RedundantCondition |
|
| 49 | - * @psalm-suppress TypeDoesNotContainType |
|
| 50 | - */ |
|
| 51 | - if (\Memcached::HAVE_IGBINARY) { |
|
| 52 | - $defaultOptions[\Memcached::OPT_SERIALIZER] |
|
| 53 | - = \Memcached::SERIALIZER_IGBINARY; |
|
| 54 | - } |
|
| 55 | - $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []); |
|
| 56 | - if (is_array($options)) { |
|
| 57 | - $options = $options + $defaultOptions; |
|
| 58 | - self::$cache->setOptions($options); |
|
| 59 | - } else { |
|
| 60 | - throw new HintException("Expected 'memcached_options' config to be an array, got $options"); |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers'); |
|
| 64 | - if (!$servers) { |
|
| 65 | - $server = \OC::$server->getSystemConfig()->getValue('memcached_server'); |
|
| 66 | - if ($server) { |
|
| 67 | - $servers = [$server]; |
|
| 68 | - } else { |
|
| 69 | - $servers = [['localhost', 11211]]; |
|
| 70 | - } |
|
| 71 | - } |
|
| 72 | - self::$cache->addServers($servers); |
|
| 73 | - } |
|
| 74 | - } |
|
| 75 | - |
|
| 76 | - /** |
|
| 77 | - * entries in XCache gets namespaced to prevent collisions between owncloud instances and users |
|
| 78 | - */ |
|
| 79 | - protected function getNameSpace() { |
|
| 80 | - return $this->prefix; |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - public function get($key) { |
|
| 84 | - $result = self::$cache->get($this->getNameSpace() . $key); |
|
| 85 | - if ($result === false && self::$cache->getResultCode() === \Memcached::RES_NOTFOUND) { |
|
| 86 | - return null; |
|
| 87 | - } else { |
|
| 88 | - return $result; |
|
| 89 | - } |
|
| 90 | - } |
|
| 91 | - |
|
| 92 | - public function set($key, $value, $ttl = 0) { |
|
| 93 | - if ($ttl > 0) { |
|
| 94 | - $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); |
|
| 95 | - } else { |
|
| 96 | - $result = self::$cache->set($this->getNameSpace() . $key, $value); |
|
| 97 | - } |
|
| 98 | - return $result || $this->isSuccess(); |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - public function hasKey($key) { |
|
| 102 | - self::$cache->get($this->getNameSpace() . $key); |
|
| 103 | - return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; |
|
| 104 | - } |
|
| 105 | - |
|
| 106 | - public function remove($key) { |
|
| 107 | - $result = self::$cache->delete($this->getNameSpace() . $key); |
|
| 108 | - return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND; |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - public function clear($prefix = '') { |
|
| 112 | - // Newer Memcached doesn't like getAllKeys(), flush everything |
|
| 113 | - self::$cache->flush(); |
|
| 114 | - return true; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - /** |
|
| 118 | - * Set a value in the cache if it's not already stored |
|
| 119 | - * |
|
| 120 | - * @param string $key |
|
| 121 | - * @param mixed $value |
|
| 122 | - * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 |
|
| 123 | - * @return bool |
|
| 124 | - */ |
|
| 125 | - public function add($key, $value, $ttl = 0) { |
|
| 126 | - $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl); |
|
| 127 | - return $result || $this->isSuccess(); |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - /** |
|
| 131 | - * Increase a stored number |
|
| 132 | - * |
|
| 133 | - * @param string $key |
|
| 134 | - * @param int $step |
|
| 135 | - * @return int | bool |
|
| 136 | - */ |
|
| 137 | - public function inc($key, $step = 1) { |
|
| 138 | - $this->add($key, 0); |
|
| 139 | - $result = self::$cache->increment($this->getPrefix() . $key, $step); |
|
| 140 | - |
|
| 141 | - if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
|
| 142 | - return false; |
|
| 143 | - } |
|
| 144 | - |
|
| 145 | - return $result; |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - /** |
|
| 149 | - * Decrease a stored number |
|
| 150 | - * |
|
| 151 | - * @param string $key |
|
| 152 | - * @param int $step |
|
| 153 | - * @return int | bool |
|
| 154 | - */ |
|
| 155 | - public function dec($key, $step = 1) { |
|
| 156 | - $result = self::$cache->decrement($this->getPrefix() . $key, $step); |
|
| 157 | - |
|
| 158 | - if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
|
| 159 | - return false; |
|
| 160 | - } |
|
| 161 | - |
|
| 162 | - return $result; |
|
| 163 | - } |
|
| 164 | - |
|
| 165 | - public static function isAvailable(): bool { |
|
| 166 | - return extension_loaded('memcached'); |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - private function isSuccess(): bool { |
|
| 170 | - return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; |
|
| 171 | - } |
|
| 14 | + use CASTrait; |
|
| 15 | + |
|
| 16 | + /** |
|
| 17 | + * @var \Memcached $cache |
|
| 18 | + */ |
|
| 19 | + private static $cache = null; |
|
| 20 | + |
|
| 21 | + use CADTrait; |
|
| 22 | + |
|
| 23 | + public function __construct($prefix = '') { |
|
| 24 | + parent::__construct($prefix); |
|
| 25 | + if (is_null(self::$cache)) { |
|
| 26 | + self::$cache = new \Memcached(); |
|
| 27 | + |
|
| 28 | + $defaultOptions = [ |
|
| 29 | + \Memcached::OPT_CONNECT_TIMEOUT => 50, |
|
| 30 | + \Memcached::OPT_RETRY_TIMEOUT => 50, |
|
| 31 | + \Memcached::OPT_SEND_TIMEOUT => 50, |
|
| 32 | + \Memcached::OPT_RECV_TIMEOUT => 50, |
|
| 33 | + \Memcached::OPT_POLL_TIMEOUT => 50, |
|
| 34 | + |
|
| 35 | + // Enable compression |
|
| 36 | + \Memcached::OPT_COMPRESSION => true, |
|
| 37 | + |
|
| 38 | + // Turn on consistent hashing |
|
| 39 | + \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, |
|
| 40 | + |
|
| 41 | + // Enable Binary Protocol |
|
| 42 | + \Memcached::OPT_BINARY_PROTOCOL => true, |
|
| 43 | + ]; |
|
| 44 | + /** |
|
| 45 | + * By default enable igbinary serializer if available |
|
| 46 | + * |
|
| 47 | + * Psalm checks depend on if igbinary is installed or not with memcached |
|
| 48 | + * @psalm-suppress RedundantCondition |
|
| 49 | + * @psalm-suppress TypeDoesNotContainType |
|
| 50 | + */ |
|
| 51 | + if (\Memcached::HAVE_IGBINARY) { |
|
| 52 | + $defaultOptions[\Memcached::OPT_SERIALIZER] |
|
| 53 | + = \Memcached::SERIALIZER_IGBINARY; |
|
| 54 | + } |
|
| 55 | + $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []); |
|
| 56 | + if (is_array($options)) { |
|
| 57 | + $options = $options + $defaultOptions; |
|
| 58 | + self::$cache->setOptions($options); |
|
| 59 | + } else { |
|
| 60 | + throw new HintException("Expected 'memcached_options' config to be an array, got $options"); |
|
| 61 | + } |
|
| 62 | + |
|
| 63 | + $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers'); |
|
| 64 | + if (!$servers) { |
|
| 65 | + $server = \OC::$server->getSystemConfig()->getValue('memcached_server'); |
|
| 66 | + if ($server) { |
|
| 67 | + $servers = [$server]; |
|
| 68 | + } else { |
|
| 69 | + $servers = [['localhost', 11211]]; |
|
| 70 | + } |
|
| 71 | + } |
|
| 72 | + self::$cache->addServers($servers); |
|
| 73 | + } |
|
| 74 | + } |
|
| 75 | + |
|
| 76 | + /** |
|
| 77 | + * entries in XCache gets namespaced to prevent collisions between owncloud instances and users |
|
| 78 | + */ |
|
| 79 | + protected function getNameSpace() { |
|
| 80 | + return $this->prefix; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + public function get($key) { |
|
| 84 | + $result = self::$cache->get($this->getNameSpace() . $key); |
|
| 85 | + if ($result === false && self::$cache->getResultCode() === \Memcached::RES_NOTFOUND) { |
|
| 86 | + return null; |
|
| 87 | + } else { |
|
| 88 | + return $result; |
|
| 89 | + } |
|
| 90 | + } |
|
| 91 | + |
|
| 92 | + public function set($key, $value, $ttl = 0) { |
|
| 93 | + if ($ttl > 0) { |
|
| 94 | + $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); |
|
| 95 | + } else { |
|
| 96 | + $result = self::$cache->set($this->getNameSpace() . $key, $value); |
|
| 97 | + } |
|
| 98 | + return $result || $this->isSuccess(); |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + public function hasKey($key) { |
|
| 102 | + self::$cache->get($this->getNameSpace() . $key); |
|
| 103 | + return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; |
|
| 104 | + } |
|
| 105 | + |
|
| 106 | + public function remove($key) { |
|
| 107 | + $result = self::$cache->delete($this->getNameSpace() . $key); |
|
| 108 | + return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND; |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + public function clear($prefix = '') { |
|
| 112 | + // Newer Memcached doesn't like getAllKeys(), flush everything |
|
| 113 | + self::$cache->flush(); |
|
| 114 | + return true; |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + /** |
|
| 118 | + * Set a value in the cache if it's not already stored |
|
| 119 | + * |
|
| 120 | + * @param string $key |
|
| 121 | + * @param mixed $value |
|
| 122 | + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 |
|
| 123 | + * @return bool |
|
| 124 | + */ |
|
| 125 | + public function add($key, $value, $ttl = 0) { |
|
| 126 | + $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl); |
|
| 127 | + return $result || $this->isSuccess(); |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + /** |
|
| 131 | + * Increase a stored number |
|
| 132 | + * |
|
| 133 | + * @param string $key |
|
| 134 | + * @param int $step |
|
| 135 | + * @return int | bool |
|
| 136 | + */ |
|
| 137 | + public function inc($key, $step = 1) { |
|
| 138 | + $this->add($key, 0); |
|
| 139 | + $result = self::$cache->increment($this->getPrefix() . $key, $step); |
|
| 140 | + |
|
| 141 | + if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
|
| 142 | + return false; |
|
| 143 | + } |
|
| 144 | + |
|
| 145 | + return $result; |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + /** |
|
| 149 | + * Decrease a stored number |
|
| 150 | + * |
|
| 151 | + * @param string $key |
|
| 152 | + * @param int $step |
|
| 153 | + * @return int | bool |
|
| 154 | + */ |
|
| 155 | + public function dec($key, $step = 1) { |
|
| 156 | + $result = self::$cache->decrement($this->getPrefix() . $key, $step); |
|
| 157 | + |
|
| 158 | + if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
|
| 159 | + return false; |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + return $result; |
|
| 163 | + } |
|
| 164 | + |
|
| 165 | + public static function isAvailable(): bool { |
|
| 166 | + return extension_loaded('memcached'); |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + private function isSuccess(): bool { |
|
| 170 | + return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; |
|
| 171 | + } |
|
| 172 | 172 | } |
@@ -81,7 +81,7 @@ discard block |
||
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | public function get($key) { |
| 84 | - $result = self::$cache->get($this->getNameSpace() . $key); |
|
| 84 | + $result = self::$cache->get($this->getNameSpace().$key); |
|
| 85 | 85 | if ($result === false && self::$cache->getResultCode() === \Memcached::RES_NOTFOUND) { |
| 86 | 86 | return null; |
| 87 | 87 | } else { |
@@ -91,20 +91,20 @@ discard block |
||
| 91 | 91 | |
| 92 | 92 | public function set($key, $value, $ttl = 0) { |
| 93 | 93 | if ($ttl > 0) { |
| 94 | - $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); |
|
| 94 | + $result = self::$cache->set($this->getNameSpace().$key, $value, $ttl); |
|
| 95 | 95 | } else { |
| 96 | - $result = self::$cache->set($this->getNameSpace() . $key, $value); |
|
| 96 | + $result = self::$cache->set($this->getNameSpace().$key, $value); |
|
| 97 | 97 | } |
| 98 | 98 | return $result || $this->isSuccess(); |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | 101 | public function hasKey($key) { |
| 102 | - self::$cache->get($this->getNameSpace() . $key); |
|
| 102 | + self::$cache->get($this->getNameSpace().$key); |
|
| 103 | 103 | return self::$cache->getResultCode() === \Memcached::RES_SUCCESS; |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | public function remove($key) { |
| 107 | - $result = self::$cache->delete($this->getNameSpace() . $key); |
|
| 107 | + $result = self::$cache->delete($this->getNameSpace().$key); |
|
| 108 | 108 | return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND; |
| 109 | 109 | } |
| 110 | 110 | |
@@ -123,7 +123,7 @@ discard block |
||
| 123 | 123 | * @return bool |
| 124 | 124 | */ |
| 125 | 125 | public function add($key, $value, $ttl = 0) { |
| 126 | - $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl); |
|
| 126 | + $result = self::$cache->add($this->getPrefix().$key, $value, $ttl); |
|
| 127 | 127 | return $result || $this->isSuccess(); |
| 128 | 128 | } |
| 129 | 129 | |
@@ -136,7 +136,7 @@ discard block |
||
| 136 | 136 | */ |
| 137 | 137 | public function inc($key, $step = 1) { |
| 138 | 138 | $this->add($key, 0); |
| 139 | - $result = self::$cache->increment($this->getPrefix() . $key, $step); |
|
| 139 | + $result = self::$cache->increment($this->getPrefix().$key, $step); |
|
| 140 | 140 | |
| 141 | 141 | if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
| 142 | 142 | return false; |
@@ -153,7 +153,7 @@ discard block |
||
| 153 | 153 | * @return int | bool |
| 154 | 154 | */ |
| 155 | 155 | public function dec($key, $step = 1) { |
| 156 | - $result = self::$cache->decrement($this->getPrefix() . $key, $step); |
|
| 156 | + $result = self::$cache->decrement($this->getPrefix().$key, $step); |
|
| 157 | 157 | |
| 158 | 158 | if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) { |
| 159 | 159 | return false; |
@@ -32,392 +32,392 @@ |
||
| 32 | 32 | * } |
| 33 | 33 | */ |
| 34 | 34 | class OC_Helper { |
| 35 | - private static $templateManager; |
|
| 36 | - private static ?ICacheFactory $cacheFactory = null; |
|
| 37 | - private static ?bool $quotaIncludeExternalStorage = null; |
|
| 38 | - |
|
| 39 | - /** |
|
| 40 | - * Recursive copying of folders |
|
| 41 | - * @param string $src source folder |
|
| 42 | - * @param string $dest target folder |
|
| 43 | - * @return void |
|
| 44 | - * @deprecated 32.0.0 - use \OCP\Files\Folder::copy |
|
| 45 | - */ |
|
| 46 | - public static function copyr($src, $dest) { |
|
| 47 | - if (!file_exists($src)) { |
|
| 48 | - return; |
|
| 49 | - } |
|
| 50 | - |
|
| 51 | - if (is_dir($src)) { |
|
| 52 | - if (!is_dir($dest)) { |
|
| 53 | - mkdir($dest); |
|
| 54 | - } |
|
| 55 | - $files = scandir($src); |
|
| 56 | - foreach ($files as $file) { |
|
| 57 | - if ($file != '.' && $file != '..') { |
|
| 58 | - self::copyr("$src/$file", "$dest/$file"); |
|
| 59 | - } |
|
| 60 | - } |
|
| 61 | - } else { |
|
| 62 | - $validator = \OCP\Server::get(FilenameValidator::class); |
|
| 63 | - if (!$validator->isForbidden($src)) { |
|
| 64 | - copy($src, $dest); |
|
| 65 | - } |
|
| 66 | - } |
|
| 67 | - } |
|
| 68 | - |
|
| 69 | - /** |
|
| 70 | - * @deprecated 18.0.0 |
|
| 71 | - * @return \OC\Files\Type\TemplateManager |
|
| 72 | - */ |
|
| 73 | - public static function getFileTemplateManager() { |
|
| 74 | - if (!self::$templateManager) { |
|
| 75 | - self::$templateManager = new \OC\Files\Type\TemplateManager(); |
|
| 76 | - } |
|
| 77 | - return self::$templateManager; |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - /** |
|
| 81 | - * detect if a given program is found in the search PATH |
|
| 82 | - * |
|
| 83 | - * @param string $name |
|
| 84 | - * @param bool $path |
|
| 85 | - * @internal param string $program name |
|
| 86 | - * @internal param string $optional search path, defaults to $PATH |
|
| 87 | - * @return bool true if executable program found in path |
|
| 88 | - * @deprecated 32.0.0 use the \OCP\IBinaryFinder |
|
| 89 | - */ |
|
| 90 | - public static function canExecute($name, $path = false) { |
|
| 91 | - // path defaults to PATH from environment if not set |
|
| 92 | - if ($path === false) { |
|
| 93 | - $path = getenv('PATH'); |
|
| 94 | - } |
|
| 95 | - // we look for an executable file of that name |
|
| 96 | - $exts = ['']; |
|
| 97 | - $check_fn = 'is_executable'; |
|
| 98 | - // Default check will be done with $path directories : |
|
| 99 | - $dirs = explode(PATH_SEPARATOR, (string)$path); |
|
| 100 | - // WARNING : We have to check if open_basedir is enabled : |
|
| 101 | - $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir'); |
|
| 102 | - if ($obd != 'none') { |
|
| 103 | - $obd_values = explode(PATH_SEPARATOR, $obd); |
|
| 104 | - if (count($obd_values) > 0 && $obd_values[0]) { |
|
| 105 | - // open_basedir is in effect ! |
|
| 106 | - // We need to check if the program is in one of these dirs : |
|
| 107 | - $dirs = $obd_values; |
|
| 108 | - } |
|
| 109 | - } |
|
| 110 | - foreach ($dirs as $dir) { |
|
| 111 | - foreach ($exts as $ext) { |
|
| 112 | - if ($check_fn("$dir/$name" . $ext)) { |
|
| 113 | - return true; |
|
| 114 | - } |
|
| 115 | - } |
|
| 116 | - } |
|
| 117 | - return false; |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - /** |
|
| 121 | - * copy the contents of one stream to another |
|
| 122 | - * |
|
| 123 | - * @param resource $source |
|
| 124 | - * @param resource $target |
|
| 125 | - * @return array the number of bytes copied and result |
|
| 126 | - * @deprecated 5.0.0 - Use \OCP\Files::streamCopy |
|
| 127 | - */ |
|
| 128 | - public static function streamCopy($source, $target) { |
|
| 129 | - return \OCP\Files::streamCopy($source, $target, true); |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - /** |
|
| 133 | - * Adds a suffix to the name in case the file exists |
|
| 134 | - * |
|
| 135 | - * @param string $path |
|
| 136 | - * @param string $filename |
|
| 137 | - * @return string |
|
| 138 | - */ |
|
| 139 | - public static function buildNotExistingFileName($path, $filename) { |
|
| 140 | - $view = \OC\Files\Filesystem::getView(); |
|
| 141 | - return self::buildNotExistingFileNameForView($path, $filename, $view); |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - /** |
|
| 145 | - * Adds a suffix to the name in case the file exists |
|
| 146 | - * |
|
| 147 | - * @param string $path |
|
| 148 | - * @param string $filename |
|
| 149 | - * @return string |
|
| 150 | - */ |
|
| 151 | - public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) { |
|
| 152 | - if ($path === '/') { |
|
| 153 | - $path = ''; |
|
| 154 | - } |
|
| 155 | - if ($pos = strrpos($filename, '.')) { |
|
| 156 | - $name = substr($filename, 0, $pos); |
|
| 157 | - $ext = substr($filename, $pos); |
|
| 158 | - } else { |
|
| 159 | - $name = $filename; |
|
| 160 | - $ext = ''; |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - $newpath = $path . '/' . $filename; |
|
| 164 | - if ($view->file_exists($newpath)) { |
|
| 165 | - if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) { |
|
| 166 | - //Replace the last "(number)" with "(number+1)" |
|
| 167 | - $last_match = count($matches[0]) - 1; |
|
| 168 | - $counter = $matches[1][$last_match][0] + 1; |
|
| 169 | - $offset = $matches[0][$last_match][1]; |
|
| 170 | - $match_length = strlen($matches[0][$last_match][0]); |
|
| 171 | - } else { |
|
| 172 | - $counter = 2; |
|
| 173 | - $match_length = 0; |
|
| 174 | - $offset = false; |
|
| 175 | - } |
|
| 176 | - do { |
|
| 177 | - if ($offset) { |
|
| 178 | - //Replace the last "(number)" with "(number+1)" |
|
| 179 | - $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length); |
|
| 180 | - } else { |
|
| 181 | - $newname = $name . ' (' . $counter . ')'; |
|
| 182 | - } |
|
| 183 | - $newpath = $path . '/' . $newname . $ext; |
|
| 184 | - $counter++; |
|
| 185 | - } while ($view->file_exists($newpath)); |
|
| 186 | - } |
|
| 187 | - |
|
| 188 | - return $newpath; |
|
| 189 | - } |
|
| 190 | - |
|
| 191 | - /** |
|
| 192 | - * Checks if a function is available |
|
| 193 | - * |
|
| 194 | - * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead |
|
| 195 | - */ |
|
| 196 | - public static function is_function_enabled(string $function_name): bool { |
|
| 197 | - return Util::isFunctionEnabled($function_name); |
|
| 198 | - } |
|
| 199 | - |
|
| 200 | - /** |
|
| 201 | - * Try to find a program |
|
| 202 | - * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly |
|
| 203 | - */ |
|
| 204 | - public static function findBinaryPath(string $program): ?string { |
|
| 205 | - $result = Server::get(IBinaryFinder::class)->findBinaryPath($program); |
|
| 206 | - return $result !== false ? $result : null; |
|
| 207 | - } |
|
| 208 | - |
|
| 209 | - /** |
|
| 210 | - * Calculate the disc space for the given path |
|
| 211 | - * |
|
| 212 | - * BEWARE: this requires that Util::setupFS() was called |
|
| 213 | - * already ! |
|
| 214 | - * |
|
| 215 | - * @param string $path |
|
| 216 | - * @param \OCP\Files\FileInfo $rootInfo (optional) |
|
| 217 | - * @param bool $includeMountPoints whether to include mount points in the size calculation |
|
| 218 | - * @param bool $useCache whether to use the cached quota values |
|
| 219 | - * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct |
|
| 220 | - * @return StorageInfo |
|
| 221 | - * @throws \OCP\Files\NotFoundException |
|
| 222 | - */ |
|
| 223 | - public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) { |
|
| 224 | - if (!self::$cacheFactory) { |
|
| 225 | - self::$cacheFactory = Server::get(ICacheFactory::class); |
|
| 226 | - } |
|
| 227 | - $memcache = self::$cacheFactory->createLocal('storage_info'); |
|
| 228 | - |
|
| 229 | - // return storage info without adding mount points |
|
| 230 | - if (self::$quotaIncludeExternalStorage === null) { |
|
| 231 | - self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); |
|
| 232 | - } |
|
| 233 | - |
|
| 234 | - $view = Filesystem::getView(); |
|
| 235 | - if (!$view) { |
|
| 236 | - throw new \OCP\Files\NotFoundException(); |
|
| 237 | - } |
|
| 238 | - $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path)); |
|
| 239 | - |
|
| 240 | - $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude'); |
|
| 241 | - if ($useCache) { |
|
| 242 | - $cached = $memcache->get($cacheKey); |
|
| 243 | - if ($cached) { |
|
| 244 | - return $cached; |
|
| 245 | - } |
|
| 246 | - } |
|
| 247 | - |
|
| 248 | - if (!$rootInfo) { |
|
| 249 | - $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false); |
|
| 250 | - } |
|
| 251 | - if (!$rootInfo instanceof \OCP\Files\FileInfo) { |
|
| 252 | - throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing'); |
|
| 253 | - } |
|
| 254 | - $used = $rootInfo->getSize($includeMountPoints); |
|
| 255 | - if ($used < 0) { |
|
| 256 | - $used = 0.0; |
|
| 257 | - } |
|
| 258 | - /** @var int|float $quota */ |
|
| 259 | - $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; |
|
| 260 | - $mount = $rootInfo->getMountPoint(); |
|
| 261 | - $storage = $mount->getStorage(); |
|
| 262 | - $sourceStorage = $storage; |
|
| 263 | - if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { |
|
| 264 | - self::$quotaIncludeExternalStorage = false; |
|
| 265 | - } |
|
| 266 | - if (self::$quotaIncludeExternalStorage) { |
|
| 267 | - if ($storage->instanceOfStorage('\OC\Files\Storage\Home') |
|
| 268 | - || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') |
|
| 269 | - ) { |
|
| 270 | - /** @var \OC\Files\Storage\Home $storage */ |
|
| 271 | - $user = $storage->getUser(); |
|
| 272 | - } else { |
|
| 273 | - $user = \OC::$server->getUserSession()->getUser(); |
|
| 274 | - } |
|
| 275 | - $quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN; |
|
| 276 | - if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { |
|
| 277 | - // always get free space / total space from root + mount points |
|
| 278 | - return self::getGlobalStorageInfo($quota, $user, $mount); |
|
| 279 | - } |
|
| 280 | - } |
|
| 281 | - |
|
| 282 | - // TODO: need a better way to get total space from storage |
|
| 283 | - if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) { |
|
| 284 | - /** @var \OC\Files\Storage\Wrapper\Quota $storage */ |
|
| 285 | - $quota = $sourceStorage->getQuota(); |
|
| 286 | - } |
|
| 287 | - try { |
|
| 288 | - $free = $sourceStorage->free_space($rootInfo->getInternalPath()); |
|
| 289 | - if (is_bool($free)) { |
|
| 290 | - $free = 0.0; |
|
| 291 | - } |
|
| 292 | - } catch (\Exception $e) { |
|
| 293 | - if ($path === '') { |
|
| 294 | - throw $e; |
|
| 295 | - } |
|
| 296 | - /** @var LoggerInterface $logger */ |
|
| 297 | - $logger = \OC::$server->get(LoggerInterface::class); |
|
| 298 | - $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]); |
|
| 299 | - $rootInfo = self::getStorageInfo(''); |
|
| 300 | - $memcache->set($cacheKey, $rootInfo, 5 * 60); |
|
| 301 | - return $rootInfo; |
|
| 302 | - } |
|
| 303 | - if ($free >= 0) { |
|
| 304 | - $total = $free + $used; |
|
| 305 | - } else { |
|
| 306 | - $total = $free; //either unknown or unlimited |
|
| 307 | - } |
|
| 308 | - if ($total > 0) { |
|
| 309 | - if ($quota > 0 && $total > $quota) { |
|
| 310 | - $total = $quota; |
|
| 311 | - } |
|
| 312 | - // prevent division by zero or error codes (negative values) |
|
| 313 | - $relative = round(($used / $total) * 10000) / 100; |
|
| 314 | - } else { |
|
| 315 | - $relative = 0; |
|
| 316 | - } |
|
| 317 | - |
|
| 318 | - /* |
|
| 35 | + private static $templateManager; |
|
| 36 | + private static ?ICacheFactory $cacheFactory = null; |
|
| 37 | + private static ?bool $quotaIncludeExternalStorage = null; |
|
| 38 | + |
|
| 39 | + /** |
|
| 40 | + * Recursive copying of folders |
|
| 41 | + * @param string $src source folder |
|
| 42 | + * @param string $dest target folder |
|
| 43 | + * @return void |
|
| 44 | + * @deprecated 32.0.0 - use \OCP\Files\Folder::copy |
|
| 45 | + */ |
|
| 46 | + public static function copyr($src, $dest) { |
|
| 47 | + if (!file_exists($src)) { |
|
| 48 | + return; |
|
| 49 | + } |
|
| 50 | + |
|
| 51 | + if (is_dir($src)) { |
|
| 52 | + if (!is_dir($dest)) { |
|
| 53 | + mkdir($dest); |
|
| 54 | + } |
|
| 55 | + $files = scandir($src); |
|
| 56 | + foreach ($files as $file) { |
|
| 57 | + if ($file != '.' && $file != '..') { |
|
| 58 | + self::copyr("$src/$file", "$dest/$file"); |
|
| 59 | + } |
|
| 60 | + } |
|
| 61 | + } else { |
|
| 62 | + $validator = \OCP\Server::get(FilenameValidator::class); |
|
| 63 | + if (!$validator->isForbidden($src)) { |
|
| 64 | + copy($src, $dest); |
|
| 65 | + } |
|
| 66 | + } |
|
| 67 | + } |
|
| 68 | + |
|
| 69 | + /** |
|
| 70 | + * @deprecated 18.0.0 |
|
| 71 | + * @return \OC\Files\Type\TemplateManager |
|
| 72 | + */ |
|
| 73 | + public static function getFileTemplateManager() { |
|
| 74 | + if (!self::$templateManager) { |
|
| 75 | + self::$templateManager = new \OC\Files\Type\TemplateManager(); |
|
| 76 | + } |
|
| 77 | + return self::$templateManager; |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + /** |
|
| 81 | + * detect if a given program is found in the search PATH |
|
| 82 | + * |
|
| 83 | + * @param string $name |
|
| 84 | + * @param bool $path |
|
| 85 | + * @internal param string $program name |
|
| 86 | + * @internal param string $optional search path, defaults to $PATH |
|
| 87 | + * @return bool true if executable program found in path |
|
| 88 | + * @deprecated 32.0.0 use the \OCP\IBinaryFinder |
|
| 89 | + */ |
|
| 90 | + public static function canExecute($name, $path = false) { |
|
| 91 | + // path defaults to PATH from environment if not set |
|
| 92 | + if ($path === false) { |
|
| 93 | + $path = getenv('PATH'); |
|
| 94 | + } |
|
| 95 | + // we look for an executable file of that name |
|
| 96 | + $exts = ['']; |
|
| 97 | + $check_fn = 'is_executable'; |
|
| 98 | + // Default check will be done with $path directories : |
|
| 99 | + $dirs = explode(PATH_SEPARATOR, (string)$path); |
|
| 100 | + // WARNING : We have to check if open_basedir is enabled : |
|
| 101 | + $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir'); |
|
| 102 | + if ($obd != 'none') { |
|
| 103 | + $obd_values = explode(PATH_SEPARATOR, $obd); |
|
| 104 | + if (count($obd_values) > 0 && $obd_values[0]) { |
|
| 105 | + // open_basedir is in effect ! |
|
| 106 | + // We need to check if the program is in one of these dirs : |
|
| 107 | + $dirs = $obd_values; |
|
| 108 | + } |
|
| 109 | + } |
|
| 110 | + foreach ($dirs as $dir) { |
|
| 111 | + foreach ($exts as $ext) { |
|
| 112 | + if ($check_fn("$dir/$name" . $ext)) { |
|
| 113 | + return true; |
|
| 114 | + } |
|
| 115 | + } |
|
| 116 | + } |
|
| 117 | + return false; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + /** |
|
| 121 | + * copy the contents of one stream to another |
|
| 122 | + * |
|
| 123 | + * @param resource $source |
|
| 124 | + * @param resource $target |
|
| 125 | + * @return array the number of bytes copied and result |
|
| 126 | + * @deprecated 5.0.0 - Use \OCP\Files::streamCopy |
|
| 127 | + */ |
|
| 128 | + public static function streamCopy($source, $target) { |
|
| 129 | + return \OCP\Files::streamCopy($source, $target, true); |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + /** |
|
| 133 | + * Adds a suffix to the name in case the file exists |
|
| 134 | + * |
|
| 135 | + * @param string $path |
|
| 136 | + * @param string $filename |
|
| 137 | + * @return string |
|
| 138 | + */ |
|
| 139 | + public static function buildNotExistingFileName($path, $filename) { |
|
| 140 | + $view = \OC\Files\Filesystem::getView(); |
|
| 141 | + return self::buildNotExistingFileNameForView($path, $filename, $view); |
|
| 142 | + } |
|
| 143 | + |
|
| 144 | + /** |
|
| 145 | + * Adds a suffix to the name in case the file exists |
|
| 146 | + * |
|
| 147 | + * @param string $path |
|
| 148 | + * @param string $filename |
|
| 149 | + * @return string |
|
| 150 | + */ |
|
| 151 | + public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) { |
|
| 152 | + if ($path === '/') { |
|
| 153 | + $path = ''; |
|
| 154 | + } |
|
| 155 | + if ($pos = strrpos($filename, '.')) { |
|
| 156 | + $name = substr($filename, 0, $pos); |
|
| 157 | + $ext = substr($filename, $pos); |
|
| 158 | + } else { |
|
| 159 | + $name = $filename; |
|
| 160 | + $ext = ''; |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + $newpath = $path . '/' . $filename; |
|
| 164 | + if ($view->file_exists($newpath)) { |
|
| 165 | + if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) { |
|
| 166 | + //Replace the last "(number)" with "(number+1)" |
|
| 167 | + $last_match = count($matches[0]) - 1; |
|
| 168 | + $counter = $matches[1][$last_match][0] + 1; |
|
| 169 | + $offset = $matches[0][$last_match][1]; |
|
| 170 | + $match_length = strlen($matches[0][$last_match][0]); |
|
| 171 | + } else { |
|
| 172 | + $counter = 2; |
|
| 173 | + $match_length = 0; |
|
| 174 | + $offset = false; |
|
| 175 | + } |
|
| 176 | + do { |
|
| 177 | + if ($offset) { |
|
| 178 | + //Replace the last "(number)" with "(number+1)" |
|
| 179 | + $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length); |
|
| 180 | + } else { |
|
| 181 | + $newname = $name . ' (' . $counter . ')'; |
|
| 182 | + } |
|
| 183 | + $newpath = $path . '/' . $newname . $ext; |
|
| 184 | + $counter++; |
|
| 185 | + } while ($view->file_exists($newpath)); |
|
| 186 | + } |
|
| 187 | + |
|
| 188 | + return $newpath; |
|
| 189 | + } |
|
| 190 | + |
|
| 191 | + /** |
|
| 192 | + * Checks if a function is available |
|
| 193 | + * |
|
| 194 | + * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead |
|
| 195 | + */ |
|
| 196 | + public static function is_function_enabled(string $function_name): bool { |
|
| 197 | + return Util::isFunctionEnabled($function_name); |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + /** |
|
| 201 | + * Try to find a program |
|
| 202 | + * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly |
|
| 203 | + */ |
|
| 204 | + public static function findBinaryPath(string $program): ?string { |
|
| 205 | + $result = Server::get(IBinaryFinder::class)->findBinaryPath($program); |
|
| 206 | + return $result !== false ? $result : null; |
|
| 207 | + } |
|
| 208 | + |
|
| 209 | + /** |
|
| 210 | + * Calculate the disc space for the given path |
|
| 211 | + * |
|
| 212 | + * BEWARE: this requires that Util::setupFS() was called |
|
| 213 | + * already ! |
|
| 214 | + * |
|
| 215 | + * @param string $path |
|
| 216 | + * @param \OCP\Files\FileInfo $rootInfo (optional) |
|
| 217 | + * @param bool $includeMountPoints whether to include mount points in the size calculation |
|
| 218 | + * @param bool $useCache whether to use the cached quota values |
|
| 219 | + * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct |
|
| 220 | + * @return StorageInfo |
|
| 221 | + * @throws \OCP\Files\NotFoundException |
|
| 222 | + */ |
|
| 223 | + public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) { |
|
| 224 | + if (!self::$cacheFactory) { |
|
| 225 | + self::$cacheFactory = Server::get(ICacheFactory::class); |
|
| 226 | + } |
|
| 227 | + $memcache = self::$cacheFactory->createLocal('storage_info'); |
|
| 228 | + |
|
| 229 | + // return storage info without adding mount points |
|
| 230 | + if (self::$quotaIncludeExternalStorage === null) { |
|
| 231 | + self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); |
|
| 232 | + } |
|
| 233 | + |
|
| 234 | + $view = Filesystem::getView(); |
|
| 235 | + if (!$view) { |
|
| 236 | + throw new \OCP\Files\NotFoundException(); |
|
| 237 | + } |
|
| 238 | + $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path)); |
|
| 239 | + |
|
| 240 | + $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude'); |
|
| 241 | + if ($useCache) { |
|
| 242 | + $cached = $memcache->get($cacheKey); |
|
| 243 | + if ($cached) { |
|
| 244 | + return $cached; |
|
| 245 | + } |
|
| 246 | + } |
|
| 247 | + |
|
| 248 | + if (!$rootInfo) { |
|
| 249 | + $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false); |
|
| 250 | + } |
|
| 251 | + if (!$rootInfo instanceof \OCP\Files\FileInfo) { |
|
| 252 | + throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing'); |
|
| 253 | + } |
|
| 254 | + $used = $rootInfo->getSize($includeMountPoints); |
|
| 255 | + if ($used < 0) { |
|
| 256 | + $used = 0.0; |
|
| 257 | + } |
|
| 258 | + /** @var int|float $quota */ |
|
| 259 | + $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; |
|
| 260 | + $mount = $rootInfo->getMountPoint(); |
|
| 261 | + $storage = $mount->getStorage(); |
|
| 262 | + $sourceStorage = $storage; |
|
| 263 | + if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { |
|
| 264 | + self::$quotaIncludeExternalStorage = false; |
|
| 265 | + } |
|
| 266 | + if (self::$quotaIncludeExternalStorage) { |
|
| 267 | + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') |
|
| 268 | + || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') |
|
| 269 | + ) { |
|
| 270 | + /** @var \OC\Files\Storage\Home $storage */ |
|
| 271 | + $user = $storage->getUser(); |
|
| 272 | + } else { |
|
| 273 | + $user = \OC::$server->getUserSession()->getUser(); |
|
| 274 | + } |
|
| 275 | + $quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN; |
|
| 276 | + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { |
|
| 277 | + // always get free space / total space from root + mount points |
|
| 278 | + return self::getGlobalStorageInfo($quota, $user, $mount); |
|
| 279 | + } |
|
| 280 | + } |
|
| 281 | + |
|
| 282 | + // TODO: need a better way to get total space from storage |
|
| 283 | + if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) { |
|
| 284 | + /** @var \OC\Files\Storage\Wrapper\Quota $storage */ |
|
| 285 | + $quota = $sourceStorage->getQuota(); |
|
| 286 | + } |
|
| 287 | + try { |
|
| 288 | + $free = $sourceStorage->free_space($rootInfo->getInternalPath()); |
|
| 289 | + if (is_bool($free)) { |
|
| 290 | + $free = 0.0; |
|
| 291 | + } |
|
| 292 | + } catch (\Exception $e) { |
|
| 293 | + if ($path === '') { |
|
| 294 | + throw $e; |
|
| 295 | + } |
|
| 296 | + /** @var LoggerInterface $logger */ |
|
| 297 | + $logger = \OC::$server->get(LoggerInterface::class); |
|
| 298 | + $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]); |
|
| 299 | + $rootInfo = self::getStorageInfo(''); |
|
| 300 | + $memcache->set($cacheKey, $rootInfo, 5 * 60); |
|
| 301 | + return $rootInfo; |
|
| 302 | + } |
|
| 303 | + if ($free >= 0) { |
|
| 304 | + $total = $free + $used; |
|
| 305 | + } else { |
|
| 306 | + $total = $free; //either unknown or unlimited |
|
| 307 | + } |
|
| 308 | + if ($total > 0) { |
|
| 309 | + if ($quota > 0 && $total > $quota) { |
|
| 310 | + $total = $quota; |
|
| 311 | + } |
|
| 312 | + // prevent division by zero or error codes (negative values) |
|
| 313 | + $relative = round(($used / $total) * 10000) / 100; |
|
| 314 | + } else { |
|
| 315 | + $relative = 0; |
|
| 316 | + } |
|
| 317 | + |
|
| 318 | + /* |
|
| 319 | 319 | * \OCA\Files_Sharing\External\Storage returns the cloud ID as the owner for the storage. |
| 320 | 320 | * It is unnecessary to query the user manager for the display name, as it won't have this information. |
| 321 | 321 | */ |
| 322 | - $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class); |
|
| 323 | - |
|
| 324 | - $ownerId = $storage->getOwner($path); |
|
| 325 | - $ownerDisplayName = ''; |
|
| 326 | - |
|
| 327 | - if ($isRemoteShare === false && $ownerId !== false) { |
|
| 328 | - $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? ''; |
|
| 329 | - } |
|
| 330 | - |
|
| 331 | - if (substr_count($mount->getMountPoint(), '/') < 3) { |
|
| 332 | - $mountPoint = ''; |
|
| 333 | - } else { |
|
| 334 | - [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); |
|
| 335 | - } |
|
| 336 | - |
|
| 337 | - $info = [ |
|
| 338 | - 'free' => $free, |
|
| 339 | - 'used' => $used, |
|
| 340 | - 'quota' => $quota, |
|
| 341 | - 'total' => $total, |
|
| 342 | - 'relative' => $relative, |
|
| 343 | - 'owner' => $ownerId, |
|
| 344 | - 'ownerDisplayName' => $ownerDisplayName, |
|
| 345 | - 'mountType' => $mount->getMountType(), |
|
| 346 | - 'mountPoint' => trim($mountPoint, '/'), |
|
| 347 | - ]; |
|
| 348 | - |
|
| 349 | - if ($isRemoteShare === false && $ownerId !== false && $path === '/') { |
|
| 350 | - // If path is root, store this as last known quota usage for this user |
|
| 351 | - \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative); |
|
| 352 | - } |
|
| 353 | - |
|
| 354 | - $memcache->set($cacheKey, $info, 5 * 60); |
|
| 355 | - |
|
| 356 | - return $info; |
|
| 357 | - } |
|
| 358 | - |
|
| 359 | - /** |
|
| 360 | - * Get storage info including all mount points and quota |
|
| 361 | - * |
|
| 362 | - * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct |
|
| 363 | - * @return StorageInfo |
|
| 364 | - */ |
|
| 365 | - private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array { |
|
| 366 | - $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); |
|
| 367 | - /** @var int|float $used */ |
|
| 368 | - $used = $rootInfo['size']; |
|
| 369 | - if ($used < 0) { |
|
| 370 | - $used = 0.0; |
|
| 371 | - } |
|
| 372 | - |
|
| 373 | - $total = $quota; |
|
| 374 | - /** @var int|float $free */ |
|
| 375 | - $free = $quota - $used; |
|
| 376 | - |
|
| 377 | - if ($total > 0) { |
|
| 378 | - if ($quota > 0 && $total > $quota) { |
|
| 379 | - $total = $quota; |
|
| 380 | - } |
|
| 381 | - // prevent division by zero or error codes (negative values) |
|
| 382 | - $relative = round(($used / $total) * 10000) / 100; |
|
| 383 | - } else { |
|
| 384 | - $relative = 0.0; |
|
| 385 | - } |
|
| 386 | - |
|
| 387 | - if (substr_count($mount->getMountPoint(), '/') < 3) { |
|
| 388 | - $mountPoint = ''; |
|
| 389 | - } else { |
|
| 390 | - [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); |
|
| 391 | - } |
|
| 392 | - |
|
| 393 | - return [ |
|
| 394 | - 'free' => $free, |
|
| 395 | - 'used' => $used, |
|
| 396 | - 'total' => $total, |
|
| 397 | - 'relative' => $relative, |
|
| 398 | - 'quota' => $quota, |
|
| 399 | - 'owner' => $user->getUID(), |
|
| 400 | - 'ownerDisplayName' => $user->getDisplayName(), |
|
| 401 | - 'mountType' => $mount->getMountType(), |
|
| 402 | - 'mountPoint' => trim($mountPoint, '/'), |
|
| 403 | - ]; |
|
| 404 | - } |
|
| 405 | - |
|
| 406 | - public static function clearStorageInfo(string $absolutePath): void { |
|
| 407 | - /** @var ICacheFactory $cacheFactory */ |
|
| 408 | - $cacheFactory = \OC::$server->get(ICacheFactory::class); |
|
| 409 | - $memcache = $cacheFactory->createLocal('storage_info'); |
|
| 410 | - $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::'; |
|
| 411 | - $memcache->remove($cacheKeyPrefix . 'include'); |
|
| 412 | - $memcache->remove($cacheKeyPrefix . 'exclude'); |
|
| 413 | - } |
|
| 414 | - |
|
| 415 | - /** |
|
| 416 | - * Returns whether the config file is set manually to read-only |
|
| 417 | - * @return bool |
|
| 418 | - * @deprecated 32.0.0 use the `config_is_read_only` system config directly |
|
| 419 | - */ |
|
| 420 | - public static function isReadOnlyConfigEnabled() { |
|
| 421 | - return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false); |
|
| 422 | - } |
|
| 322 | + $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class); |
|
| 323 | + |
|
| 324 | + $ownerId = $storage->getOwner($path); |
|
| 325 | + $ownerDisplayName = ''; |
|
| 326 | + |
|
| 327 | + if ($isRemoteShare === false && $ownerId !== false) { |
|
| 328 | + $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? ''; |
|
| 329 | + } |
|
| 330 | + |
|
| 331 | + if (substr_count($mount->getMountPoint(), '/') < 3) { |
|
| 332 | + $mountPoint = ''; |
|
| 333 | + } else { |
|
| 334 | + [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); |
|
| 335 | + } |
|
| 336 | + |
|
| 337 | + $info = [ |
|
| 338 | + 'free' => $free, |
|
| 339 | + 'used' => $used, |
|
| 340 | + 'quota' => $quota, |
|
| 341 | + 'total' => $total, |
|
| 342 | + 'relative' => $relative, |
|
| 343 | + 'owner' => $ownerId, |
|
| 344 | + 'ownerDisplayName' => $ownerDisplayName, |
|
| 345 | + 'mountType' => $mount->getMountType(), |
|
| 346 | + 'mountPoint' => trim($mountPoint, '/'), |
|
| 347 | + ]; |
|
| 348 | + |
|
| 349 | + if ($isRemoteShare === false && $ownerId !== false && $path === '/') { |
|
| 350 | + // If path is root, store this as last known quota usage for this user |
|
| 351 | + \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative); |
|
| 352 | + } |
|
| 353 | + |
|
| 354 | + $memcache->set($cacheKey, $info, 5 * 60); |
|
| 355 | + |
|
| 356 | + return $info; |
|
| 357 | + } |
|
| 358 | + |
|
| 359 | + /** |
|
| 360 | + * Get storage info including all mount points and quota |
|
| 361 | + * |
|
| 362 | + * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct |
|
| 363 | + * @return StorageInfo |
|
| 364 | + */ |
|
| 365 | + private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array { |
|
| 366 | + $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext'); |
|
| 367 | + /** @var int|float $used */ |
|
| 368 | + $used = $rootInfo['size']; |
|
| 369 | + if ($used < 0) { |
|
| 370 | + $used = 0.0; |
|
| 371 | + } |
|
| 372 | + |
|
| 373 | + $total = $quota; |
|
| 374 | + /** @var int|float $free */ |
|
| 375 | + $free = $quota - $used; |
|
| 376 | + |
|
| 377 | + if ($total > 0) { |
|
| 378 | + if ($quota > 0 && $total > $quota) { |
|
| 379 | + $total = $quota; |
|
| 380 | + } |
|
| 381 | + // prevent division by zero or error codes (negative values) |
|
| 382 | + $relative = round(($used / $total) * 10000) / 100; |
|
| 383 | + } else { |
|
| 384 | + $relative = 0.0; |
|
| 385 | + } |
|
| 386 | + |
|
| 387 | + if (substr_count($mount->getMountPoint(), '/') < 3) { |
|
| 388 | + $mountPoint = ''; |
|
| 389 | + } else { |
|
| 390 | + [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); |
|
| 391 | + } |
|
| 392 | + |
|
| 393 | + return [ |
|
| 394 | + 'free' => $free, |
|
| 395 | + 'used' => $used, |
|
| 396 | + 'total' => $total, |
|
| 397 | + 'relative' => $relative, |
|
| 398 | + 'quota' => $quota, |
|
| 399 | + 'owner' => $user->getUID(), |
|
| 400 | + 'ownerDisplayName' => $user->getDisplayName(), |
|
| 401 | + 'mountType' => $mount->getMountType(), |
|
| 402 | + 'mountPoint' => trim($mountPoint, '/'), |
|
| 403 | + ]; |
|
| 404 | + } |
|
| 405 | + |
|
| 406 | + public static function clearStorageInfo(string $absolutePath): void { |
|
| 407 | + /** @var ICacheFactory $cacheFactory */ |
|
| 408 | + $cacheFactory = \OC::$server->get(ICacheFactory::class); |
|
| 409 | + $memcache = $cacheFactory->createLocal('storage_info'); |
|
| 410 | + $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::'; |
|
| 411 | + $memcache->remove($cacheKeyPrefix . 'include'); |
|
| 412 | + $memcache->remove($cacheKeyPrefix . 'exclude'); |
|
| 413 | + } |
|
| 414 | + |
|
| 415 | + /** |
|
| 416 | + * Returns whether the config file is set manually to read-only |
|
| 417 | + * @return bool |
|
| 418 | + * @deprecated 32.0.0 use the `config_is_read_only` system config directly |
|
| 419 | + */ |
|
| 420 | + public static function isReadOnlyConfigEnabled() { |
|
| 421 | + return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false); |
|
| 422 | + } |
|
| 423 | 423 | } |
@@ -40,366 +40,366 @@ |
||
| 40 | 40 | * logout() |
| 41 | 41 | */ |
| 42 | 42 | class OC_User { |
| 43 | - private static $_setupedBackends = []; |
|
| 44 | - |
|
| 45 | - // bool, stores if a user want to access a resource anonymously, e.g if they open a public link |
|
| 46 | - private static $incognitoMode = false; |
|
| 47 | - |
|
| 48 | - /** |
|
| 49 | - * Adds the backend to the list of used backends |
|
| 50 | - * |
|
| 51 | - * @param string|\OCP\UserInterface $backend default: database The backend to use for user management |
|
| 52 | - * @return bool |
|
| 53 | - * @deprecated 32.0.0 Use IUserManager::registerBackend instead |
|
| 54 | - * |
|
| 55 | - * Set the User Authentication Module |
|
| 56 | - */ |
|
| 57 | - public static function useBackend($backend = 'database') { |
|
| 58 | - if ($backend instanceof \OCP\UserInterface) { |
|
| 59 | - Server::get(IUserManager::class)->registerBackend($backend); |
|
| 60 | - } else { |
|
| 61 | - // You'll never know what happens |
|
| 62 | - if ($backend === null || !is_string($backend)) { |
|
| 63 | - $backend = 'database'; |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - // Load backend |
|
| 67 | - switch ($backend) { |
|
| 68 | - case 'database': |
|
| 69 | - case 'mysql': |
|
| 70 | - case 'sqlite': |
|
| 71 | - Server::get(LoggerInterface::class)->debug('Adding user backend ' . $backend . '.', ['app' => 'core']); |
|
| 72 | - Server::get(IUserManager::class)->registerBackend(new \OC\User\Database()); |
|
| 73 | - break; |
|
| 74 | - case 'dummy': |
|
| 75 | - Server::get(IUserManager::class)->registerBackend(new \Test\Util\User\Dummy()); |
|
| 76 | - break; |
|
| 77 | - default: |
|
| 78 | - Server::get(LoggerInterface::class)->debug('Adding default user backend ' . $backend . '.', ['app' => 'core']); |
|
| 79 | - $className = 'OC_USER_' . strtoupper($backend); |
|
| 80 | - Server::get(IUserManager::class)->registerBackend(new $className()); |
|
| 81 | - break; |
|
| 82 | - } |
|
| 83 | - } |
|
| 84 | - return true; |
|
| 85 | - } |
|
| 86 | - |
|
| 87 | - /** |
|
| 88 | - * remove all used backends |
|
| 89 | - * @deprecated 32.0.0 Use IUserManager::clearBackends instead |
|
| 90 | - */ |
|
| 91 | - public static function clearBackends() { |
|
| 92 | - Server::get(IUserManager::class)->clearBackends(); |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - /** |
|
| 96 | - * setup the configured backends in config.php |
|
| 97 | - * @suppress PhanDeprecatedFunction |
|
| 98 | - */ |
|
| 99 | - public static function setupBackends() { |
|
| 100 | - OC_App::loadApps(['prelogin']); |
|
| 101 | - $backends = \OC::$server->getSystemConfig()->getValue('user_backends', []); |
|
| 102 | - if (isset($backends['default']) && !$backends['default']) { |
|
| 103 | - // clear default backends |
|
| 104 | - self::clearBackends(); |
|
| 105 | - } |
|
| 106 | - foreach ($backends as $i => $config) { |
|
| 107 | - if (!is_array($config)) { |
|
| 108 | - continue; |
|
| 109 | - } |
|
| 110 | - $class = $config['class']; |
|
| 111 | - $arguments = $config['arguments']; |
|
| 112 | - if (class_exists($class)) { |
|
| 113 | - if (!in_array($i, self::$_setupedBackends)) { |
|
| 114 | - // make a reflection object |
|
| 115 | - $reflectionObj = new ReflectionClass($class); |
|
| 116 | - |
|
| 117 | - // use Reflection to create a new instance, using the $args |
|
| 118 | - $backend = $reflectionObj->newInstanceArgs($arguments); |
|
| 119 | - self::useBackend($backend); |
|
| 120 | - self::$_setupedBackends[] = $i; |
|
| 121 | - } else { |
|
| 122 | - Server::get(LoggerInterface::class)->debug('User backend ' . $class . ' already initialized.', ['app' => 'core']); |
|
| 123 | - } |
|
| 124 | - } else { |
|
| 125 | - Server::get(LoggerInterface::class)->error('User backend ' . $class . ' not found.', ['app' => 'core']); |
|
| 126 | - } |
|
| 127 | - } |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - /** |
|
| 131 | - * Try to login a user, assuming authentication |
|
| 132 | - * has already happened (e.g. via Single Sign On). |
|
| 133 | - * |
|
| 134 | - * Log in a user and regenerate a new session. |
|
| 135 | - * |
|
| 136 | - * @param \OCP\Authentication\IApacheBackend $backend |
|
| 137 | - * @return bool |
|
| 138 | - */ |
|
| 139 | - public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) { |
|
| 140 | - $uid = $backend->getCurrentUserId(); |
|
| 141 | - $run = true; |
|
| 142 | - OC_Hook::emit('OC_User', 'pre_login', ['run' => &$run, 'uid' => $uid, 'backend' => $backend]); |
|
| 143 | - |
|
| 144 | - if ($uid) { |
|
| 145 | - if (self::getUser() !== $uid) { |
|
| 146 | - self::setUserId($uid); |
|
| 147 | - $userSession = \OC::$server->getUserSession(); |
|
| 148 | - |
|
| 149 | - /** @var IEventDispatcher $dispatcher */ |
|
| 150 | - $dispatcher = \OC::$server->get(IEventDispatcher::class); |
|
| 151 | - |
|
| 152 | - if ($userSession->getUser() && !$userSession->getUser()->isEnabled()) { |
|
| 153 | - $message = \OC::$server->getL10N('lib')->t('Account disabled'); |
|
| 154 | - throw new DisabledUserException($message); |
|
| 155 | - } |
|
| 156 | - $userSession->setLoginName($uid); |
|
| 157 | - $request = OC::$server->getRequest(); |
|
| 158 | - $password = null; |
|
| 159 | - if ($backend instanceof \OCP\Authentication\IProvideUserSecretBackend) { |
|
| 160 | - $password = $backend->getCurrentUserSecret(); |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - /** @var IEventDispatcher $dispatcher */ |
|
| 164 | - $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend)); |
|
| 165 | - |
|
| 166 | - $userSession->createSessionToken($request, $uid, $uid, $password); |
|
| 167 | - $userSession->createRememberMeToken($userSession->getUser()); |
|
| 168 | - |
|
| 169 | - if (empty($password)) { |
|
| 170 | - $tokenProvider = \OC::$server->get(IProvider::class); |
|
| 171 | - try { |
|
| 172 | - $token = $tokenProvider->getToken($userSession->getSession()->getId()); |
|
| 173 | - $token->setScope([ |
|
| 174 | - IToken::SCOPE_SKIP_PASSWORD_VALIDATION => true, |
|
| 175 | - IToken::SCOPE_FILESYSTEM => true, |
|
| 176 | - ]); |
|
| 177 | - $tokenProvider->updateToken($token); |
|
| 178 | - } catch (InvalidTokenException|WipeTokenException|SessionNotAvailableException) { |
|
| 179 | - // swallow the exceptions as we do not deal with them here |
|
| 180 | - // simply skip updating the token when is it missing |
|
| 181 | - } |
|
| 182 | - } |
|
| 183 | - |
|
| 184 | - // setup the filesystem |
|
| 185 | - OC_Util::setupFS($uid); |
|
| 186 | - // first call the post_login hooks, the login-process needs to be |
|
| 187 | - // completed before we can safely create the users folder. |
|
| 188 | - // For example encryption needs to initialize the users keys first |
|
| 189 | - // before we can create the user folder with the skeleton files |
|
| 190 | - OC_Hook::emit( |
|
| 191 | - 'OC_User', |
|
| 192 | - 'post_login', |
|
| 193 | - [ |
|
| 194 | - 'uid' => $uid, |
|
| 195 | - 'password' => $password, |
|
| 196 | - 'isTokenLogin' => false, |
|
| 197 | - ] |
|
| 198 | - ); |
|
| 199 | - $dispatcher->dispatchTyped(new UserLoggedInEvent( |
|
| 200 | - \OC::$server->get(IUserManager::class)->get($uid), |
|
| 201 | - $uid, |
|
| 202 | - null, |
|
| 203 | - false) |
|
| 204 | - ); |
|
| 205 | - |
|
| 206 | - //trigger creation of user home and /files folder |
|
| 207 | - \OC::$server->getUserFolder($uid); |
|
| 208 | - } |
|
| 209 | - return true; |
|
| 210 | - } |
|
| 211 | - return false; |
|
| 212 | - } |
|
| 213 | - |
|
| 214 | - /** |
|
| 215 | - * Verify with Apache whether user is authenticated. |
|
| 216 | - * |
|
| 217 | - * @return boolean|null |
|
| 218 | - * true: authenticated |
|
| 219 | - * false: not authenticated |
|
| 220 | - * null: not handled / no backend available |
|
| 221 | - */ |
|
| 222 | - public static function handleApacheAuth() { |
|
| 223 | - $backend = self::findFirstActiveUsedBackend(); |
|
| 224 | - if ($backend) { |
|
| 225 | - OC_App::loadApps(); |
|
| 226 | - |
|
| 227 | - //setup extra user backends |
|
| 228 | - self::setupBackends(); |
|
| 229 | - \OC::$server->getUserSession()->unsetMagicInCookie(); |
|
| 230 | - |
|
| 231 | - return self::loginWithApache($backend); |
|
| 232 | - } |
|
| 233 | - |
|
| 234 | - return null; |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - |
|
| 238 | - /** |
|
| 239 | - * Sets user id for session and triggers emit |
|
| 240 | - * |
|
| 241 | - * @param string $uid |
|
| 242 | - */ |
|
| 243 | - public static function setUserId($uid) { |
|
| 244 | - $userSession = \OC::$server->getUserSession(); |
|
| 245 | - $userManager = Server::get(IUserManager::class); |
|
| 246 | - if ($user = $userManager->get($uid)) { |
|
| 247 | - $userSession->setUser($user); |
|
| 248 | - } else { |
|
| 249 | - \OC::$server->getSession()->set('user_id', $uid); |
|
| 250 | - } |
|
| 251 | - } |
|
| 252 | - |
|
| 253 | - /** |
|
| 254 | - * Check if the user is logged in, considers also the HTTP basic credentials |
|
| 255 | - * |
|
| 256 | - * @deprecated 12.0.0 use \OC::$server->getUserSession()->isLoggedIn() |
|
| 257 | - * @return bool |
|
| 258 | - */ |
|
| 259 | - public static function isLoggedIn() { |
|
| 260 | - return \OC::$server->getUserSession()->isLoggedIn(); |
|
| 261 | - } |
|
| 262 | - |
|
| 263 | - /** |
|
| 264 | - * set incognito mode, e.g. if a user wants to open a public link |
|
| 265 | - * |
|
| 266 | - * @param bool $status |
|
| 267 | - */ |
|
| 268 | - public static function setIncognitoMode($status) { |
|
| 269 | - self::$incognitoMode = $status; |
|
| 270 | - } |
|
| 271 | - |
|
| 272 | - /** |
|
| 273 | - * get incognito mode status |
|
| 274 | - * |
|
| 275 | - * @return bool |
|
| 276 | - */ |
|
| 277 | - public static function isIncognitoMode() { |
|
| 278 | - return self::$incognitoMode; |
|
| 279 | - } |
|
| 280 | - |
|
| 281 | - /** |
|
| 282 | - * Returns the current logout URL valid for the currently logged-in user |
|
| 283 | - * |
|
| 284 | - * @param \OCP\IURLGenerator $urlGenerator |
|
| 285 | - * @return string |
|
| 286 | - */ |
|
| 287 | - public static function getLogoutUrl(\OCP\IURLGenerator $urlGenerator) { |
|
| 288 | - $backend = self::findFirstActiveUsedBackend(); |
|
| 289 | - if ($backend) { |
|
| 290 | - return $backend->getLogoutUrl(); |
|
| 291 | - } |
|
| 292 | - |
|
| 293 | - $user = \OC::$server->getUserSession()->getUser(); |
|
| 294 | - if ($user instanceof IUser) { |
|
| 295 | - $backend = $user->getBackend(); |
|
| 296 | - if ($backend instanceof \OCP\User\Backend\ICustomLogout) { |
|
| 297 | - return $backend->getLogoutUrl(); |
|
| 298 | - } |
|
| 299 | - } |
|
| 300 | - |
|
| 301 | - $logoutUrl = $urlGenerator->linkToRoute('core.login.logout'); |
|
| 302 | - $logoutUrl .= '?requesttoken=' . urlencode(\OCP\Util::callRegister()); |
|
| 303 | - |
|
| 304 | - return $logoutUrl; |
|
| 305 | - } |
|
| 306 | - |
|
| 307 | - /** |
|
| 308 | - * Check if the user is an admin user |
|
| 309 | - * |
|
| 310 | - * @param string $uid uid of the admin |
|
| 311 | - * @return bool |
|
| 312 | - */ |
|
| 313 | - public static function isAdminUser($uid) { |
|
| 314 | - $user = Server::get(IUserManager::class)->get($uid); |
|
| 315 | - $isAdmin = $user && Server::get(IGroupManager::class)->isAdmin($user->getUID()); |
|
| 316 | - return $isAdmin && self::$incognitoMode === false; |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - |
|
| 320 | - /** |
|
| 321 | - * get the user id of the user currently logged in. |
|
| 322 | - * |
|
| 323 | - * @return string|false uid or false |
|
| 324 | - */ |
|
| 325 | - public static function getUser() { |
|
| 326 | - $uid = Server::get(ISession::class)?->get('user_id'); |
|
| 327 | - if (!is_null($uid) && self::$incognitoMode === false) { |
|
| 328 | - return $uid; |
|
| 329 | - } else { |
|
| 330 | - return false; |
|
| 331 | - } |
|
| 332 | - } |
|
| 333 | - |
|
| 334 | - /** |
|
| 335 | - * Set password |
|
| 336 | - * |
|
| 337 | - * @param string $uid The username |
|
| 338 | - * @param string $password The new password |
|
| 339 | - * @param string $recoveryPassword for the encryption app to reset encryption keys |
|
| 340 | - * @return bool |
|
| 341 | - * |
|
| 342 | - * Change the password of a user |
|
| 343 | - */ |
|
| 344 | - public static function setPassword($uid, $password, $recoveryPassword = null) { |
|
| 345 | - $user = Server::get(IUserManager::class)->get($uid); |
|
| 346 | - if ($user) { |
|
| 347 | - return $user->setPassword($password, $recoveryPassword); |
|
| 348 | - } else { |
|
| 349 | - return false; |
|
| 350 | - } |
|
| 351 | - } |
|
| 352 | - |
|
| 353 | - /** |
|
| 354 | - * @param string $uid The username |
|
| 355 | - * @return string |
|
| 356 | - * |
|
| 357 | - * returns the path to the users home directory |
|
| 358 | - * @deprecated 12.0.0 Use \OC::$server->getUserManager->getHome() |
|
| 359 | - */ |
|
| 360 | - public static function getHome($uid) { |
|
| 361 | - $user = Server::get(IUserManager::class)->get($uid); |
|
| 362 | - if ($user) { |
|
| 363 | - return $user->getHome(); |
|
| 364 | - } else { |
|
| 365 | - return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; |
|
| 366 | - } |
|
| 367 | - } |
|
| 368 | - |
|
| 369 | - /** |
|
| 370 | - * Get a list of all users display name |
|
| 371 | - * |
|
| 372 | - * @param string $search |
|
| 373 | - * @param int $limit |
|
| 374 | - * @param int $offset |
|
| 375 | - * @return array associative array with all display names (value) and corresponding uids (key) |
|
| 376 | - * |
|
| 377 | - * Get a list of all display names and user ids. |
|
| 378 | - * @deprecated 12.0.0 Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead. |
|
| 379 | - */ |
|
| 380 | - public static function getDisplayNames($search = '', $limit = null, $offset = null) { |
|
| 381 | - $displayNames = []; |
|
| 382 | - $users = Server::get(IUserManager::class)->searchDisplayName($search, $limit, $offset); |
|
| 383 | - foreach ($users as $user) { |
|
| 384 | - $displayNames[$user->getUID()] = $user->getDisplayName(); |
|
| 385 | - } |
|
| 386 | - return $displayNames; |
|
| 387 | - } |
|
| 388 | - |
|
| 389 | - /** |
|
| 390 | - * Returns the first active backend from self::$_usedBackends. |
|
| 391 | - * |
|
| 392 | - * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend |
|
| 393 | - */ |
|
| 394 | - private static function findFirstActiveUsedBackend() { |
|
| 395 | - foreach (Server::get(IUserManager::class)->getBackends() as $backend) { |
|
| 396 | - if ($backend instanceof OCP\Authentication\IApacheBackend) { |
|
| 397 | - if ($backend->isSessionActive()) { |
|
| 398 | - return $backend; |
|
| 399 | - } |
|
| 400 | - } |
|
| 401 | - } |
|
| 402 | - |
|
| 403 | - return null; |
|
| 404 | - } |
|
| 43 | + private static $_setupedBackends = []; |
|
| 44 | + |
|
| 45 | + // bool, stores if a user want to access a resource anonymously, e.g if they open a public link |
|
| 46 | + private static $incognitoMode = false; |
|
| 47 | + |
|
| 48 | + /** |
|
| 49 | + * Adds the backend to the list of used backends |
|
| 50 | + * |
|
| 51 | + * @param string|\OCP\UserInterface $backend default: database The backend to use for user management |
|
| 52 | + * @return bool |
|
| 53 | + * @deprecated 32.0.0 Use IUserManager::registerBackend instead |
|
| 54 | + * |
|
| 55 | + * Set the User Authentication Module |
|
| 56 | + */ |
|
| 57 | + public static function useBackend($backend = 'database') { |
|
| 58 | + if ($backend instanceof \OCP\UserInterface) { |
|
| 59 | + Server::get(IUserManager::class)->registerBackend($backend); |
|
| 60 | + } else { |
|
| 61 | + // You'll never know what happens |
|
| 62 | + if ($backend === null || !is_string($backend)) { |
|
| 63 | + $backend = 'database'; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + // Load backend |
|
| 67 | + switch ($backend) { |
|
| 68 | + case 'database': |
|
| 69 | + case 'mysql': |
|
| 70 | + case 'sqlite': |
|
| 71 | + Server::get(LoggerInterface::class)->debug('Adding user backend ' . $backend . '.', ['app' => 'core']); |
|
| 72 | + Server::get(IUserManager::class)->registerBackend(new \OC\User\Database()); |
|
| 73 | + break; |
|
| 74 | + case 'dummy': |
|
| 75 | + Server::get(IUserManager::class)->registerBackend(new \Test\Util\User\Dummy()); |
|
| 76 | + break; |
|
| 77 | + default: |
|
| 78 | + Server::get(LoggerInterface::class)->debug('Adding default user backend ' . $backend . '.', ['app' => 'core']); |
|
| 79 | + $className = 'OC_USER_' . strtoupper($backend); |
|
| 80 | + Server::get(IUserManager::class)->registerBackend(new $className()); |
|
| 81 | + break; |
|
| 82 | + } |
|
| 83 | + } |
|
| 84 | + return true; |
|
| 85 | + } |
|
| 86 | + |
|
| 87 | + /** |
|
| 88 | + * remove all used backends |
|
| 89 | + * @deprecated 32.0.0 Use IUserManager::clearBackends instead |
|
| 90 | + */ |
|
| 91 | + public static function clearBackends() { |
|
| 92 | + Server::get(IUserManager::class)->clearBackends(); |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + /** |
|
| 96 | + * setup the configured backends in config.php |
|
| 97 | + * @suppress PhanDeprecatedFunction |
|
| 98 | + */ |
|
| 99 | + public static function setupBackends() { |
|
| 100 | + OC_App::loadApps(['prelogin']); |
|
| 101 | + $backends = \OC::$server->getSystemConfig()->getValue('user_backends', []); |
|
| 102 | + if (isset($backends['default']) && !$backends['default']) { |
|
| 103 | + // clear default backends |
|
| 104 | + self::clearBackends(); |
|
| 105 | + } |
|
| 106 | + foreach ($backends as $i => $config) { |
|
| 107 | + if (!is_array($config)) { |
|
| 108 | + continue; |
|
| 109 | + } |
|
| 110 | + $class = $config['class']; |
|
| 111 | + $arguments = $config['arguments']; |
|
| 112 | + if (class_exists($class)) { |
|
| 113 | + if (!in_array($i, self::$_setupedBackends)) { |
|
| 114 | + // make a reflection object |
|
| 115 | + $reflectionObj = new ReflectionClass($class); |
|
| 116 | + |
|
| 117 | + // use Reflection to create a new instance, using the $args |
|
| 118 | + $backend = $reflectionObj->newInstanceArgs($arguments); |
|
| 119 | + self::useBackend($backend); |
|
| 120 | + self::$_setupedBackends[] = $i; |
|
| 121 | + } else { |
|
| 122 | + Server::get(LoggerInterface::class)->debug('User backend ' . $class . ' already initialized.', ['app' => 'core']); |
|
| 123 | + } |
|
| 124 | + } else { |
|
| 125 | + Server::get(LoggerInterface::class)->error('User backend ' . $class . ' not found.', ['app' => 'core']); |
|
| 126 | + } |
|
| 127 | + } |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + /** |
|
| 131 | + * Try to login a user, assuming authentication |
|
| 132 | + * has already happened (e.g. via Single Sign On). |
|
| 133 | + * |
|
| 134 | + * Log in a user and regenerate a new session. |
|
| 135 | + * |
|
| 136 | + * @param \OCP\Authentication\IApacheBackend $backend |
|
| 137 | + * @return bool |
|
| 138 | + */ |
|
| 139 | + public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) { |
|
| 140 | + $uid = $backend->getCurrentUserId(); |
|
| 141 | + $run = true; |
|
| 142 | + OC_Hook::emit('OC_User', 'pre_login', ['run' => &$run, 'uid' => $uid, 'backend' => $backend]); |
|
| 143 | + |
|
| 144 | + if ($uid) { |
|
| 145 | + if (self::getUser() !== $uid) { |
|
| 146 | + self::setUserId($uid); |
|
| 147 | + $userSession = \OC::$server->getUserSession(); |
|
| 148 | + |
|
| 149 | + /** @var IEventDispatcher $dispatcher */ |
|
| 150 | + $dispatcher = \OC::$server->get(IEventDispatcher::class); |
|
| 151 | + |
|
| 152 | + if ($userSession->getUser() && !$userSession->getUser()->isEnabled()) { |
|
| 153 | + $message = \OC::$server->getL10N('lib')->t('Account disabled'); |
|
| 154 | + throw new DisabledUserException($message); |
|
| 155 | + } |
|
| 156 | + $userSession->setLoginName($uid); |
|
| 157 | + $request = OC::$server->getRequest(); |
|
| 158 | + $password = null; |
|
| 159 | + if ($backend instanceof \OCP\Authentication\IProvideUserSecretBackend) { |
|
| 160 | + $password = $backend->getCurrentUserSecret(); |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + /** @var IEventDispatcher $dispatcher */ |
|
| 164 | + $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend)); |
|
| 165 | + |
|
| 166 | + $userSession->createSessionToken($request, $uid, $uid, $password); |
|
| 167 | + $userSession->createRememberMeToken($userSession->getUser()); |
|
| 168 | + |
|
| 169 | + if (empty($password)) { |
|
| 170 | + $tokenProvider = \OC::$server->get(IProvider::class); |
|
| 171 | + try { |
|
| 172 | + $token = $tokenProvider->getToken($userSession->getSession()->getId()); |
|
| 173 | + $token->setScope([ |
|
| 174 | + IToken::SCOPE_SKIP_PASSWORD_VALIDATION => true, |
|
| 175 | + IToken::SCOPE_FILESYSTEM => true, |
|
| 176 | + ]); |
|
| 177 | + $tokenProvider->updateToken($token); |
|
| 178 | + } catch (InvalidTokenException|WipeTokenException|SessionNotAvailableException) { |
|
| 179 | + // swallow the exceptions as we do not deal with them here |
|
| 180 | + // simply skip updating the token when is it missing |
|
| 181 | + } |
|
| 182 | + } |
|
| 183 | + |
|
| 184 | + // setup the filesystem |
|
| 185 | + OC_Util::setupFS($uid); |
|
| 186 | + // first call the post_login hooks, the login-process needs to be |
|
| 187 | + // completed before we can safely create the users folder. |
|
| 188 | + // For example encryption needs to initialize the users keys first |
|
| 189 | + // before we can create the user folder with the skeleton files |
|
| 190 | + OC_Hook::emit( |
|
| 191 | + 'OC_User', |
|
| 192 | + 'post_login', |
|
| 193 | + [ |
|
| 194 | + 'uid' => $uid, |
|
| 195 | + 'password' => $password, |
|
| 196 | + 'isTokenLogin' => false, |
|
| 197 | + ] |
|
| 198 | + ); |
|
| 199 | + $dispatcher->dispatchTyped(new UserLoggedInEvent( |
|
| 200 | + \OC::$server->get(IUserManager::class)->get($uid), |
|
| 201 | + $uid, |
|
| 202 | + null, |
|
| 203 | + false) |
|
| 204 | + ); |
|
| 205 | + |
|
| 206 | + //trigger creation of user home and /files folder |
|
| 207 | + \OC::$server->getUserFolder($uid); |
|
| 208 | + } |
|
| 209 | + return true; |
|
| 210 | + } |
|
| 211 | + return false; |
|
| 212 | + } |
|
| 213 | + |
|
| 214 | + /** |
|
| 215 | + * Verify with Apache whether user is authenticated. |
|
| 216 | + * |
|
| 217 | + * @return boolean|null |
|
| 218 | + * true: authenticated |
|
| 219 | + * false: not authenticated |
|
| 220 | + * null: not handled / no backend available |
|
| 221 | + */ |
|
| 222 | + public static function handleApacheAuth() { |
|
| 223 | + $backend = self::findFirstActiveUsedBackend(); |
|
| 224 | + if ($backend) { |
|
| 225 | + OC_App::loadApps(); |
|
| 226 | + |
|
| 227 | + //setup extra user backends |
|
| 228 | + self::setupBackends(); |
|
| 229 | + \OC::$server->getUserSession()->unsetMagicInCookie(); |
|
| 230 | + |
|
| 231 | + return self::loginWithApache($backend); |
|
| 232 | + } |
|
| 233 | + |
|
| 234 | + return null; |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + |
|
| 238 | + /** |
|
| 239 | + * Sets user id for session and triggers emit |
|
| 240 | + * |
|
| 241 | + * @param string $uid |
|
| 242 | + */ |
|
| 243 | + public static function setUserId($uid) { |
|
| 244 | + $userSession = \OC::$server->getUserSession(); |
|
| 245 | + $userManager = Server::get(IUserManager::class); |
|
| 246 | + if ($user = $userManager->get($uid)) { |
|
| 247 | + $userSession->setUser($user); |
|
| 248 | + } else { |
|
| 249 | + \OC::$server->getSession()->set('user_id', $uid); |
|
| 250 | + } |
|
| 251 | + } |
|
| 252 | + |
|
| 253 | + /** |
|
| 254 | + * Check if the user is logged in, considers also the HTTP basic credentials |
|
| 255 | + * |
|
| 256 | + * @deprecated 12.0.0 use \OC::$server->getUserSession()->isLoggedIn() |
|
| 257 | + * @return bool |
|
| 258 | + */ |
|
| 259 | + public static function isLoggedIn() { |
|
| 260 | + return \OC::$server->getUserSession()->isLoggedIn(); |
|
| 261 | + } |
|
| 262 | + |
|
| 263 | + /** |
|
| 264 | + * set incognito mode, e.g. if a user wants to open a public link |
|
| 265 | + * |
|
| 266 | + * @param bool $status |
|
| 267 | + */ |
|
| 268 | + public static function setIncognitoMode($status) { |
|
| 269 | + self::$incognitoMode = $status; |
|
| 270 | + } |
|
| 271 | + |
|
| 272 | + /** |
|
| 273 | + * get incognito mode status |
|
| 274 | + * |
|
| 275 | + * @return bool |
|
| 276 | + */ |
|
| 277 | + public static function isIncognitoMode() { |
|
| 278 | + return self::$incognitoMode; |
|
| 279 | + } |
|
| 280 | + |
|
| 281 | + /** |
|
| 282 | + * Returns the current logout URL valid for the currently logged-in user |
|
| 283 | + * |
|
| 284 | + * @param \OCP\IURLGenerator $urlGenerator |
|
| 285 | + * @return string |
|
| 286 | + */ |
|
| 287 | + public static function getLogoutUrl(\OCP\IURLGenerator $urlGenerator) { |
|
| 288 | + $backend = self::findFirstActiveUsedBackend(); |
|
| 289 | + if ($backend) { |
|
| 290 | + return $backend->getLogoutUrl(); |
|
| 291 | + } |
|
| 292 | + |
|
| 293 | + $user = \OC::$server->getUserSession()->getUser(); |
|
| 294 | + if ($user instanceof IUser) { |
|
| 295 | + $backend = $user->getBackend(); |
|
| 296 | + if ($backend instanceof \OCP\User\Backend\ICustomLogout) { |
|
| 297 | + return $backend->getLogoutUrl(); |
|
| 298 | + } |
|
| 299 | + } |
|
| 300 | + |
|
| 301 | + $logoutUrl = $urlGenerator->linkToRoute('core.login.logout'); |
|
| 302 | + $logoutUrl .= '?requesttoken=' . urlencode(\OCP\Util::callRegister()); |
|
| 303 | + |
|
| 304 | + return $logoutUrl; |
|
| 305 | + } |
|
| 306 | + |
|
| 307 | + /** |
|
| 308 | + * Check if the user is an admin user |
|
| 309 | + * |
|
| 310 | + * @param string $uid uid of the admin |
|
| 311 | + * @return bool |
|
| 312 | + */ |
|
| 313 | + public static function isAdminUser($uid) { |
|
| 314 | + $user = Server::get(IUserManager::class)->get($uid); |
|
| 315 | + $isAdmin = $user && Server::get(IGroupManager::class)->isAdmin($user->getUID()); |
|
| 316 | + return $isAdmin && self::$incognitoMode === false; |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + |
|
| 320 | + /** |
|
| 321 | + * get the user id of the user currently logged in. |
|
| 322 | + * |
|
| 323 | + * @return string|false uid or false |
|
| 324 | + */ |
|
| 325 | + public static function getUser() { |
|
| 326 | + $uid = Server::get(ISession::class)?->get('user_id'); |
|
| 327 | + if (!is_null($uid) && self::$incognitoMode === false) { |
|
| 328 | + return $uid; |
|
| 329 | + } else { |
|
| 330 | + return false; |
|
| 331 | + } |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + /** |
|
| 335 | + * Set password |
|
| 336 | + * |
|
| 337 | + * @param string $uid The username |
|
| 338 | + * @param string $password The new password |
|
| 339 | + * @param string $recoveryPassword for the encryption app to reset encryption keys |
|
| 340 | + * @return bool |
|
| 341 | + * |
|
| 342 | + * Change the password of a user |
|
| 343 | + */ |
|
| 344 | + public static function setPassword($uid, $password, $recoveryPassword = null) { |
|
| 345 | + $user = Server::get(IUserManager::class)->get($uid); |
|
| 346 | + if ($user) { |
|
| 347 | + return $user->setPassword($password, $recoveryPassword); |
|
| 348 | + } else { |
|
| 349 | + return false; |
|
| 350 | + } |
|
| 351 | + } |
|
| 352 | + |
|
| 353 | + /** |
|
| 354 | + * @param string $uid The username |
|
| 355 | + * @return string |
|
| 356 | + * |
|
| 357 | + * returns the path to the users home directory |
|
| 358 | + * @deprecated 12.0.0 Use \OC::$server->getUserManager->getHome() |
|
| 359 | + */ |
|
| 360 | + public static function getHome($uid) { |
|
| 361 | + $user = Server::get(IUserManager::class)->get($uid); |
|
| 362 | + if ($user) { |
|
| 363 | + return $user->getHome(); |
|
| 364 | + } else { |
|
| 365 | + return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; |
|
| 366 | + } |
|
| 367 | + } |
|
| 368 | + |
|
| 369 | + /** |
|
| 370 | + * Get a list of all users display name |
|
| 371 | + * |
|
| 372 | + * @param string $search |
|
| 373 | + * @param int $limit |
|
| 374 | + * @param int $offset |
|
| 375 | + * @return array associative array with all display names (value) and corresponding uids (key) |
|
| 376 | + * |
|
| 377 | + * Get a list of all display names and user ids. |
|
| 378 | + * @deprecated 12.0.0 Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead. |
|
| 379 | + */ |
|
| 380 | + public static function getDisplayNames($search = '', $limit = null, $offset = null) { |
|
| 381 | + $displayNames = []; |
|
| 382 | + $users = Server::get(IUserManager::class)->searchDisplayName($search, $limit, $offset); |
|
| 383 | + foreach ($users as $user) { |
|
| 384 | + $displayNames[$user->getUID()] = $user->getDisplayName(); |
|
| 385 | + } |
|
| 386 | + return $displayNames; |
|
| 387 | + } |
|
| 388 | + |
|
| 389 | + /** |
|
| 390 | + * Returns the first active backend from self::$_usedBackends. |
|
| 391 | + * |
|
| 392 | + * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend |
|
| 393 | + */ |
|
| 394 | + private static function findFirstActiveUsedBackend() { |
|
| 395 | + foreach (Server::get(IUserManager::class)->getBackends() as $backend) { |
|
| 396 | + if ($backend instanceof OCP\Authentication\IApacheBackend) { |
|
| 397 | + if ($backend->isSessionActive()) { |
|
| 398 | + return $backend; |
|
| 399 | + } |
|
| 400 | + } |
|
| 401 | + } |
|
| 402 | + |
|
| 403 | + return null; |
|
| 404 | + } |
|
| 405 | 405 | } |