1<?php 2/** 3 * @author Adam Williamson <awilliam@redhat.com> 4 * @author Andreas Fischer <bantu@owncloud.com> 5 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 6 * @author Bart Visscher <bartv@thisnet.nl> 7 * @author Bernhard Posselt <dev@bernhard-posselt.com> 8 * @author Birk Borkason <daniel.niccoli@gmail.com> 9 * @author Björn Schießle <bjoern@schiessle.org> 10 * @author Brice Maron <brice@bmaron.net> 11 * @author Christopher Schäpers <kondou@ts.unde.re> 12 * @author Christoph Wurst <christoph@owncloud.com> 13 * @author Clark Tomlinson <fallen013@gmail.com> 14 * @author cmeh <cmeh@users.noreply.github.com> 15 * @author Florin Peter <github@florin-peter.de> 16 * @author Frank Karlitschek <frank@karlitschek.de> 17 * @author Georg Ehrke <georg@owncloud.com> 18 * @author helix84 <helix84@centrum.sk> 19 * @author Individual IT Services <info@individual-it.net> 20 * @author Jakob Sack <mail@jakobsack.de> 21 * @author Joas Schilling <coding@schilljs.com> 22 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 23 * @author Kawohl <john@owncloud.com> 24 * @author Lukas Reschke <lukas@statuscode.ch> 25 * @author Markus Goetz <markus@woboq.com> 26 * @author Martin Mattel <martin.mattel@diemattels.at> 27 * @author Marvin Thomas Rabe <mrabe@marvinrabe.de> 28 * @author Michael Gapczynski <GapczynskiM@gmail.com> 29 * @author Morris Jobke <hey@morrisjobke.de> 30 * @author Philipp Schaffrath <github@philippschaffrath.de> 31 * @author Robin Appelman <icewind@owncloud.com> 32 * @author Robin McCorkell <robin@mccorkell.me.uk> 33 * @author Roeland Jago Douma <rullzer@owncloud.com> 34 * @author Stefan Rado <owncloud@sradonia.net> 35 * @author Stefan Weil <sw@weilnetz.de> 36 * @author Thomas Müller <thomas.mueller@tmit.eu> 37 * @author Thomas Tanghus <thomas@tanghus.net> 38 * @author Victor Dubiniuk <dubiniuk@owncloud.com> 39 * @author Vincent Petry <pvince81@owncloud.com> 40 * @author Volkan Gezer <volkangezer@gmail.com> 41 * 42 * @copyright Copyright (c) 2018, ownCloud GmbH 43 * @license AGPL-3.0 44 * 45 * This code is free software: you can redistribute it and/or modify 46 * it under the terms of the GNU Affero General Public License, version 3, 47 * as published by the Free Software Foundation. 48 * 49 * This program is distributed in the hope that it will be useful, 50 * but WITHOUT ANY WARRANTY; without even the implied warranty of 51 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 52 * GNU Affero General Public License for more details. 53 * 54 * You should have received a copy of the GNU Affero General Public License, version 3, 55 * along with this program. If not, see <http://www.gnu.org/licenses/> 56 * 57 */ 58 59use OC\Security\SignedUrl\Verifier; 60use OC\User\LoginException; 61use OCP\Authentication\Exceptions\AccountCheckException; 62use OCP\Files\NoReadAccessException; 63use OCP\IConfig; 64use OCP\IGroupManager; 65use OCP\IUser; 66use OCP\License\ILicenseManager; 67 68class OC_Util { 69 public static $scripts = []; 70 public static $styles = []; 71 public static $headers = []; 72 private static $rootMounted = false; 73 private static $fsSetup = false; 74 private static $version; 75 public const EDITION_COMMUNITY = 'Community'; 76 public const EDITION_ENTERPRISE = 'Enterprise'; 77 78 protected static function getAppManager() { 79 return \OC::$server->getAppManager(); 80 } 81 82 private static function initLocalStorageRootFS() { 83 // mount local file backend as root 84 $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); 85 //first set up the local "root" storage 86 \OC\Files\Filesystem::initMountManager(); 87 if (!self::$rootMounted) { 88 \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', ['datadir' => $configDataDirectory], '/'); 89 self::$rootMounted = true; 90 } 91 } 92 93 /** 94 * mounting an object storage as the root fs will in essence remove the 95 * necessity of a data folder being present. 96 * TODO make home storage aware of this and use the object storage instead of local disk access 97 * 98 * @param array $config containing 'class' and optional 'arguments' 99 */ 100 private static function initObjectStoreRootFS($config) { 101 // check misconfiguration 102 if (empty($config['class'])) { 103 \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); 104 } 105 if (!isset($config['arguments'])) { 106 $config['arguments'] = []; 107 } 108 109 // instantiate object store implementation 110 $name = $config['class']; 111 if (\strpos($name, 'OCA\\') === 0 && \substr_count($name, '\\') >= 2) { 112 $segments = \explode('\\', $name); 113 OC_App::loadApp(\strtolower($segments[1])); 114 } 115 $config['arguments']['objectstore'] = new $config['class']($config['arguments']); 116 // mount with plain / root object store implementation 117 $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; 118 119 // mount object storage as root 120 \OC\Files\Filesystem::initMountManager(); 121 if (!self::$rootMounted) { 122 \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); 123 self::$rootMounted = true; 124 } 125 } 126 127 /** 128 * Can be set up 129 * 130 * @param string $user 131 * @return boolean 132 * @description configure the initial filesystem based on the configuration 133 */ 134 public static function setupFS($user = '') { 135 //setting up the filesystem twice can only lead to trouble 136 if (self::$fsSetup) { 137 return false; 138 } 139 140 \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem'); 141 142 // If we are not forced to load a specific user we load the one that is logged in 143 if ($user === null) { 144 $user = ''; 145 } elseif ($user == "" && OC_User::isLoggedIn()) { 146 $user = OC_User::getUser(); 147 } 148 149 // load all filesystem apps before, so no setup-hook gets lost 150 OC_App::loadApps(['filesystem']); 151 152 // the filesystem will finish when $user is not empty, 153 // mark fs setup here to avoid doing the setup from loading 154 // OC_Filesystem 155 if ($user != '') { 156 self::$fsSetup = true; 157 } 158 159 \OC\Files\Filesystem::initMountManager(); 160 161 \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); 162 \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { 163 if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) { 164 /** @var \OC\Files\Storage\Common $storage */ 165 '@phan-var \OC\Files\Storage\Common $storage'; 166 $storage->setMountOptions($mount->getOptions()); 167 } 168 return $storage; 169 }); 170 171 \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { 172 if (!$mount->getOption('enable_sharing', true)) { 173 return new \OC\Files\Storage\Wrapper\PermissionsMask([ 174 'storage' => $storage, 175 'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE 176 ]); 177 } 178 return $storage; 179 }); 180 181 \OC\Files\Filesystem::addStorageWrapper('read_only', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { 182 if ($mount->getOption('read_only', false)) { 183 return new \OC\Files\Storage\Wrapper\PermissionsMask([ 184 'storage' => $storage, 185 'mask' => \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE 186 ]); 187 } 188 return $storage; 189 }); 190 191 // install storage availability wrapper, before most other wrappers 192 \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, $storage) { 193 if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { 194 return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); 195 } 196 return $storage; 197 }); 198 199 // install storage checksum wrapper 200 \OC\Files\Filesystem::addStorageWrapper('oc_checksum', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) { 201 if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { 202 return new \OC\Files\Storage\Wrapper\Checksum(['storage' => $storage]); 203 } 204 205 return $storage; 206 }, 1); 207 208 \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { 209 if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { 210 return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); 211 } 212 return $storage; 213 }); 214 215 \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { 216 // set up quota for home storages, even for other users 217 // which can happen when using sharing 218 219 /** 220 * @var \OC\Files\Storage\Storage $storage 221 */ 222 if ($storage->instanceOfStorage('\OC\Files\Storage\Home') 223 || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') 224 ) { 225 /** @var \OC\Files\Storage\Home $storage */ 226 if (\is_object($storage->getUser())) { 227 $user = $storage->getUser()->getUID(); 228 $quota = OC_Util::getUserQuota($user); 229 if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { 230 return new \OC\Files\Storage\Wrapper\Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']); 231 } 232 } 233 } 234 235 return $storage; 236 }); 237 238 OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]); 239 \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true); 240 241 // Make users storage readonly if he is a guest or in a read_only group 242 243 $isGuest = \OC::$server->getConfig()->getUserValue( 244 $user, 245 'owncloud', 246 'isGuest', 247 false 248 ); 249 250 if (!$isGuest) { 251 $readOnlyGroups = \json_decode(\OC::$server->getConfig()->getAppValue( 252 'core', 253 'read_only_groups', 254 '[]' 255 ), true); 256 257 if (!\is_array($readOnlyGroups)) { 258 $readOnlyGroups = []; 259 } 260 $readOnlyGroupMemberships = []; 261 if ($readOnlyGroups) { 262 $userGroups = \array_keys( 263 \OC::$server->getGroupManager()->getUserIdGroups($user) 264 ); 265 266 $readOnlyGroupMemberships = \array_intersect( 267 $readOnlyGroups, 268 $userGroups 269 ); 270 } 271 } 272 273 if ($isGuest === '1' || !empty($readOnlyGroupMemberships)) { 274 \OC\Files\Filesystem::addStorageWrapper( 275 'oc_readonly', 276 function ($mountPoint, $storage) use ($user) { 277 if ($mountPoint === '/' || $mountPoint === "/$user/") { 278 return new \OC\Files\Storage\Wrapper\ReadOnlyJail( 279 [ 280 'storage' => $storage, 281 'mask' => \OCP\Constants::PERMISSION_READ, 282 'path' => 'files' 283 ] 284 ); 285 } 286 287 return $storage; 288 } 289 ); 290 } 291 292 //check if we are using an object storage 293 $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); 294 $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', $objectStore); 295 if (isset($objectStore)) { 296 self::initObjectStoreRootFS($objectStore); 297 } else { 298 self::initLocalStorageRootFS(); 299 } 300 301 if ($user != '' && !OCP\User::userExists($user)) { 302 \OC::$server->getEventLogger()->end('setup_fs'); 303 return false; 304 } 305 306 //if we aren't logged in, there is no use to set up the filesystem 307 if ($user != "") { 308 $userDir = '/' . $user . '/files'; 309 310 //jail the user into his "home" directory 311 \OC\Files\Filesystem::init($user, $userDir); 312 313 OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user, 'user_dir' => $userDir]); 314 } 315 \OC::$server->getEventLogger()->end('setup_fs'); 316 return true; 317 } 318 319 /** 320 * check if a password is required for each public link. 321 * This is deprecated due to not reflecting all the possibilities now. Falling back to 322 * enforce password for read-only links. Note that read & write or write-only options won't 323 * be considered here 324 * 325 * @return boolean 326 * @deprecated 327 */ 328 public static function isPublicLinkPasswordRequired() { 329 $appConfig = \OC::$server->getAppConfig(); 330 $enforcePassword = $appConfig->getValue('core', 'shareapi_enforce_links_password_read_only', 'no'); 331 return ($enforcePassword === 'yes') ? true : false; 332 } 333 334 /** 335 * check if share API enforces a default expire date 336 * 337 * @return boolean 338 */ 339 public static function isDefaultExpireDateEnforced() { 340 $isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no'); 341 $enforceDefaultExpireDate = false; 342 if ($isDefaultExpireDateEnabled === 'yes') { 343 $value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no'); 344 $enforceDefaultExpireDate = ($value === 'yes') ? true : false; 345 } 346 347 return $enforceDefaultExpireDate; 348 } 349 350 /** 351 * Get the quota of a user 352 * 353 * @param string|IUser $userId 354 * @return int Quota bytes 355 */ 356 public static function getUserQuota($userId) { 357 if ($userId instanceof IUser) { 358 $user = $userId; 359 } else { 360 $user = \OC::$server->getUserManager()->get($userId); 361 } 362 if ($user === null) { 363 return \OCP\Files\FileInfo::SPACE_UNLIMITED; 364 } 365 $userQuota = $user->getQuota(); 366 if ($userQuota === null || $userQuota === 'default') { 367 $userQuota = \OC::$server->getConfig()->getAppValue('files', 'default_quota', 'none'); 368 } 369 if ($userQuota === null || $userQuota === 'none') { 370 return \OCP\Files\FileInfo::SPACE_UNLIMITED; 371 } 372 return OC_Helper::computerFileSize($userQuota); 373 } 374 375 /** 376 * copies the skeleton to the users /files 377 * 378 * @param String $userId 379 * @param \OCP\Files\Folder $userDirectory 380 * @throws \OC\HintException 381 */ 382 public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { 383 $skeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); 384 385 if (!\is_dir($skeletonDirectory)) { 386 throw new \OC\HintException('The skeleton folder '.$skeletonDirectory.' is not accessible'); 387 } 388 389 if (!empty($skeletonDirectory)) { 390 \OCP\Util::writeLog( 391 'files_skeleton', 392 'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), 393 \OCP\Util::DEBUG 394 ); 395 self::copyr($skeletonDirectory, $userDirectory); 396 // update the file cache 397 $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); 398 } 399 } 400 401 /** 402 * copies a directory recursively by using streams 403 * 404 * @param string $source 405 * @param \OCP\Files\Folder $target 406 * @return void 407 * @throws NoReadAccessException 408 */ 409 public static function copyr($source, \OCP\Files\Folder $target) { 410 $dir = @\opendir($source); 411 if ($dir === false) { 412 throw new NoReadAccessException('No read permission for folder ' . $source); 413 } 414 while (($file = \readdir($dir)) !== false) { 415 if (!\OC\Files\Filesystem::isIgnoredDir($file)) { 416 if (\is_dir($source . '/' . $file)) { 417 $child = $target->newFolder($file); 418 self::copyr($source . '/' . $file, $child); 419 } else { 420 $sourceFileHandle = @\fopen($source . '/' . $file, 'r'); 421 if ($sourceFileHandle === false) { 422 throw new NoReadAccessException('No read permission for file ' . $file); 423 } 424 $child = $target->newFile($file); 425 $targetFileHandle = $child->fopen('w'); 426 \stream_copy_to_stream($sourceFileHandle, $targetFileHandle); 427 \fclose($targetFileHandle); 428 \fclose($sourceFileHandle); 429 430 // update cache sizes 431 $cache = $target->getStorage()->getCache(); 432 if ($cache instanceof \OC\Files\Cache\Cache) { 433 $cache->correctFolderSize($child->getInternalPath()); 434 } 435 } 436 } 437 } 438 \closedir($dir); 439 } 440 441 /** 442 * @return void 443 */ 444 public static function tearDownFS() { 445 \OC\Files\Filesystem::tearDown(); 446 self::$fsSetup = false; 447 self::$rootMounted = false; 448 } 449 450 /** 451 * get the current installed version of ownCloud 452 * 453 * @return array 454 */ 455 public static function getVersion() { 456 OC_Util::loadVersion(); 457 return self::$version['OC_Version']; 458 } 459 460 /** 461 * get the current installed version string of ownCloud 462 * 463 * @return string 464 */ 465 public static function getVersionString() { 466 OC_Util::loadVersion(); 467 return self::$version['OC_VersionString']; 468 } 469 470 /** 471 * @description get the current installed edition of ownCloud. 472 * There is the community edition that returns "Community" and 473 * the enterprise edition that returns "Enterprise". 474 * @return string 475 */ 476 public static function getEditionString() { 477 $licenseManager = \OC::$server->getLicenseManager(); 478 // TODO: Need to open method in the licenseManager to expose the license type 479 $licenseMessage = $licenseManager->getLicenseMessageFor('core'); 480 $licenseState = $licenseMessage['license_state']; 481 $licenseType = $licenseMessage['type']; 482 if ($licenseState !== ILicenseManager::LICENSE_STATE_MISSING && 483 $licenseState !== ILicenseManager::LICENSE_STATE_INVALID 484 ) { 485 // if it's a expired demo key, we need to return community 486 if ($licenseState === ILicenseManager::LICENSE_STATE_EXPIRED && $licenseType !== 0) { 487 /** 488 * TODO: licenseType === 0 is documented as "normal" license in the 489 * ILicenseManager::getLicenseMessageFor method 490 * This needs to be properly exposed as constant by the ILicenseManager 491 */ 492 return OC_Util::EDITION_COMMUNITY; 493 } 494 return OC_Util::EDITION_ENTERPRISE; 495 } else { 496 return OC_Util::EDITION_COMMUNITY; 497 } 498 } 499 500 /** 501 * @description get the update channel of the current installed of ownCloud. 502 * @return string 503 */ 504 public static function getChannel() { 505 OC_Util::loadVersion(); 506 return self::$version['OC_Channel']; 507 } 508 509 /** 510 * @description get the build number of the current installed of ownCloud. 511 * @return string 512 */ 513 public static function getBuild() { 514 OC_Util::loadVersion(); 515 return self::$version['OC_Build']; 516 } 517 518 /** 519 * @description load the version.php into the session as cache 520 */ 521 private static function loadVersion() { 522 require __DIR__ . '/../../../version.php'; 523 /** @var $OC_Version string */ 524 /** @var $OC_VersionString string */ 525 /** @var $OC_Build string */ 526 /** @var $OC_Channel string */ 527 self::$version = [ 528 'OC_Version' => $OC_Version, 529 'OC_VersionString' => $OC_VersionString, 530 'OC_Build' => $OC_Build, 531 'OC_Channel' => $OC_Channel, 532 ]; 533 534 // Allow overriding update channel 535 536 if (\OC::$server->getSystemConfig()->getValue('installed', false)) { 537 $channel = \OC::$server->getConfig()->getAppValue('core', 'OC_Channel', $OC_Channel); 538 self::$version['OC_Channel'] = $channel; 539 } 540 } 541 542 /** 543 * generates a path for JS/CSS files. If no application is provided it will create the path for core. 544 * 545 * @param string $application application to get the files from 546 * @param string $directory directory within this application (css, js, vendor, etc) 547 * @param string $file the file inside of the above folder 548 * @return string the path 549 */ 550 private static function generatePath($application, $directory, $file) { 551 if ($file === null) { 552 $file = $application; 553 $application = ""; 554 } 555 if (!empty($application)) { 556 return "$application/$directory/$file"; 557 } else { 558 return "$directory/$file"; 559 } 560 } 561 562 /** 563 * add a javascript file 564 * 565 * @param string $application application id 566 * @param string|null $file filename 567 * @param bool $prepend prepend the Script to the beginning of the list 568 * @return void 569 */ 570 public static function addScript($application, $file = null, $prepend = false) { 571 $path = OC_Util::generatePath($application, 'js', $file); 572 573 // core js files need separate handling 574 if ($application !== 'core' && $file !== null) { 575 self::addTranslations($application); 576 } 577 self::addExternalResource($application, $prepend, $path, "script"); 578 } 579 580 /** 581 * add a javascript file from the vendor sub folder 582 * 583 * @param string $application application id 584 * @param string|null $file filename 585 * @param bool $prepend prepend the Script to the beginning of the list 586 * @return void 587 */ 588 public static function addVendorScript($application, $file = null, $prepend = false) { 589 $path = OC_Util::generatePath($application, 'vendor', $file); 590 self::addExternalResource($application, $prepend, $path, "script"); 591 } 592 593 /** 594 * add a translation JS file 595 * 596 * @param string $application application id 597 * @param string $languageCode language code, defaults to the current language 598 * @param bool $prepend prepend the Script to the beginning of the list 599 */ 600 public static function addTranslations($application, $languageCode = null, $prepend = false) { 601 if ($languageCode === null) { 602 $languageCode = \OC::$server->getL10NFactory()->findLanguage($application); 603 } 604 if (!empty($application)) { 605 $path = "$application/l10n/$languageCode"; 606 } else { 607 $path = "l10n/$languageCode"; 608 } 609 self::addExternalResource($application, $prepend, $path, "script"); 610 } 611 612 /** 613 * add a css file 614 * 615 * @param string $application application id 616 * @param string|null $file filename 617 * @param bool $prepend prepend the Style to the beginning of the list 618 * @return void 619 */ 620 public static function addStyle($application, $file = null, $prepend = false) { 621 $path = OC_Util::generatePath($application, 'css', $file); 622 self::addExternalResource($application, $prepend, $path, "style"); 623 } 624 625 /** 626 * add a css file from the vendor sub folder 627 * 628 * @param string $application application id 629 * @param string|null $file filename 630 * @param bool $prepend prepend the Style to the beginning of the list 631 * @return void 632 */ 633 public static function addVendorStyle($application, $file = null, $prepend = false) { 634 $path = OC_Util::generatePath($application, 'vendor', $file); 635 self::addExternalResource($application, $prepend, $path, "style"); 636 } 637 638 /** 639 * add an external resource css/js file 640 * 641 * @param string $application application id 642 * @param bool $prepend prepend the file to the beginning of the list 643 * @param string $path 644 * @param string $type (script or style) 645 * @return void 646 */ 647 private static function addExternalResource($application, $prepend, $path, $type = "script") { 648 if ($type === "style") { 649 if (!\in_array($path, self::$styles)) { 650 if ($prepend === true) { 651 \array_unshift(self::$styles, $path); 652 } else { 653 self::$styles[] = $path; 654 } 655 } 656 } elseif ($type === "script") { 657 if (!\in_array($path, self::$scripts)) { 658 if ($prepend === true) { 659 \array_unshift(self::$scripts, $path); 660 } else { 661 self::$scripts [] = $path; 662 } 663 } 664 } 665 } 666 667 /** 668 * Add a custom element to the header 669 * If $text is null then the element will be written as empty element. 670 * So use "" to get a closing tag. 671 * @param string $tag tag name of the element 672 * @param array $attributes array of attributes for the element 673 * @param string $text the text content for the element 674 */ 675 public static function addHeader($tag, $attributes, $text=null) { 676 self::$headers[] = [ 677 'tag' => $tag, 678 'attributes' => $attributes, 679 'text' => $text 680 ]; 681 } 682 683 /** 684 * formats a timestamp in the "right" way 685 * 686 * @param int $timestamp 687 * @param bool $dateOnly option to omit time from the result 688 * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to 689 * @return string timestamp 690 * 691 * @deprecated Use \OC::$server->query('DateTimeFormatter') instead 692 */ 693 public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) { 694 if ($timeZone !== null && !$timeZone instanceof \DateTimeZone) { 695 $timeZone = new \DateTimeZone($timeZone); 696 } 697 698 /** @var \OC\DateTimeFormatter $formatter */ 699 $formatter = \OC::$server->query('DateTimeFormatter'); 700 if ($dateOnly) { 701 return $formatter->formatDate($timestamp, 'long', $timeZone); 702 } 703 return $formatter->formatDateTime($timestamp, 'long', 'long', $timeZone); 704 } 705 706 /** 707 * check if the current server configuration is suitable for ownCloud 708 * 709 * @param \OCP\IConfig $config 710 * @return array arrays with error messages and hints 711 */ 712 public static function checkServer(\OCP\IConfig $config) { 713 $l = \OC::$server->getL10N('lib'); 714 $errors = []; 715 $CONFIG_DATADIRECTORY = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data'); 716 717 if (!self::needUpgrade($config) && $config->getSystemValue('installed', false)) { 718 // this check needs to be done every time 719 $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY); 720 } 721 722 // Assume that if checkServer() succeeded before in this session, then all is fine. 723 if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) { 724 return $errors; 725 } 726 727 $webServerRestart = false; 728 $setup = new \OC\Setup( 729 $config, 730 \OC::$server->getIniWrapper(), 731 \OC::$server->getL10N('lib'), 732 new \OC_Defaults(), 733 \OC::$server->getLogger(), 734 \OC::$server->getSecureRandom() 735 ); 736 737 $urlGenerator = \OC::$server->getURLGenerator(); 738 739 $availableDatabases = $setup->getSupportedDatabases(); 740 if (empty($availableDatabases)) { 741 $errors[] = [ 742 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'), 743 'hint' => '' //TODO: sane hint 744 ]; 745 $webServerRestart = true; 746 } 747 748 // Check if config folder is writable. 749 if (!\OC::$server->getConfig()->isSystemConfigReadOnly()) { 750 if (!\is_writable(OC::$configDir) or !\is_readable(OC::$configDir)) { 751 $errors[] = [ 752 'error' => $l->t('Cannot write into "config" directory'), 753 'hint' => $l->t( 754 'This can usually be fixed by ' 755 . '%sgiving the webserver write access to the config directory%s.', 756 ['<a href="' . $urlGenerator->linkToDocs('admin-dir_permissions') . '" target="_blank" rel="noreferrer">', '</a>'] 757 ) 758 ]; 759 } 760 } 761 762 // Create root dir. 763 if ($config->getSystemValue('installed', false)) { 764 if (!\is_dir($CONFIG_DATADIRECTORY)) { 765 $success = @\mkdir($CONFIG_DATADIRECTORY); 766 if ($success) { 767 $errors = \array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); 768 } else { 769 $errors[] = [ 770 'error' => $l->t('Cannot create "data" directory'), 771 'hint' => $l->t( 772 'This can usually be fixed by ' 773 . '<a href="%s" target="_blank" rel="noreferrer">giving the webserver write access to the root directory</a>.', 774 [$urlGenerator->linkToDocs('admin-dir_permissions')] 775 ) 776 ]; 777 } 778 } elseif (!\is_writable($CONFIG_DATADIRECTORY) or !\is_readable($CONFIG_DATADIRECTORY)) { 779 //common hint for all file permissions error messages 780 $permissionsHint = $l->t( 781 'Permissions can usually be fixed by ' 782 . '%sgiving the webserver write access to the root directory%s.', 783 ['<a href="' . $urlGenerator->linkToDocs('admin-dir_permissions') . '" target="_blank" rel="noreferrer">', '</a>'] 784 ); 785 $errors[] = [ 786 'error' => 'Your Data directory is not writable by ownCloud', 787 'hint' => $permissionsHint 788 ]; 789 } else { 790 $errors = \array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); 791 } 792 } 793 794 if (!OC_Util::isSetLocaleWorking()) { 795 $errors[] = [ 796 'error' => $l->t( 797 'Setting locale to %s failed', 798 ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/' 799 . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8'] 800 ), 801 'hint' => $l->t('Please install one of these locales on your system and restart your webserver.') 802 ]; 803 } 804 805 // Contains the dependencies that should be checked against 806 // classes = class_exists 807 // functions = function_exists 808 // defined = defined 809 // ini = ini_get 810 // If the dependency is not found the missing module name is shown to the EndUser 811 // When adding new checks always verify that they pass on Travis as well 812 // for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini 813 $dependencies = [ 814 'classes' => [ 815 'ZipArchive' => 'zip', 816 'DOMDocument' => 'dom', 817 'XMLWriter' => 'XMLWriter', 818 'XMLReader' => 'XMLReader', 819 'Collator' => 'intl', 820 ], 821 'functions' => [ 822 'xml_parser_create' => 'libxml', 823 'mb_strcut' => 'mb multibyte', 824 'ctype_digit' => 'ctype', 825 'json_encode' => 'JSON', 826 'gd_info' => 'GD', 827 'gzencode' => 'zlib', 828 'iconv' => 'iconv', 829 'simplexml_load_string' => 'SimpleXML', 830 'hash' => 'HASH Message Digest Framework', 831 'curl_init' => 'cURL', 832 ], 833 'defined' => [ 834 'PDO::ATTR_DRIVER_NAME' => 'PDO' 835 ], 836 'ini' => [ 837 'default_charset' => 'UTF-8', 838 ], 839 ]; 840 $missingDependencies = []; 841 $invalidIniSettings = []; 842 $moduleHint = $l->t('Please ask your server administrator to install the module.'); 843 844 /** 845 * FIXME: The dependency check does not work properly on HHVM on the moment 846 * and prevents installation. Once HHVM is more compatible with our 847 * approach to check for these values we should re-enable those 848 * checks. 849 */ 850 $iniWrapper = \OC::$server->getIniWrapper(); 851 if (!self::runningOnHhvm()) { 852 foreach ($dependencies['classes'] as $class => $module) { 853 if (!\class_exists($class)) { 854 $missingDependencies[] = $module; 855 } 856 } 857 foreach ($dependencies['functions'] as $function => $module) { 858 if (!\function_exists($function)) { 859 $missingDependencies[] = $module; 860 } 861 } 862 foreach ($dependencies['defined'] as $defined => $module) { 863 if (!\defined($defined)) { 864 $missingDependencies[] = $module; 865 } 866 } 867 foreach ($dependencies['ini'] as $setting => $expected) { 868 if (\is_bool($expected)) { 869 if ($iniWrapper->getBool($setting) !== $expected) { 870 $invalidIniSettings[] = [$setting, $expected]; 871 } 872 } 873 if (\is_int($expected)) { 874 if ($iniWrapper->getNumeric($setting) !== $expected) { 875 $invalidIniSettings[] = [$setting, $expected]; 876 } 877 } 878 if (\is_string($expected)) { 879 if (\strtolower($iniWrapper->getString($setting)) !== \strtolower($expected)) { 880 $invalidIniSettings[] = [$setting, $expected]; 881 } 882 } 883 } 884 } 885 886 foreach ($missingDependencies as $missingDependency) { 887 $errors[] = [ 888 'error' => $l->t('PHP module %s not installed.', [$missingDependency]), 889 'hint' => $moduleHint 890 ]; 891 $webServerRestart = true; 892 } 893 foreach ($invalidIniSettings as $setting) { 894 if (\is_bool($setting[1])) { 895 $setting[1] = ($setting[1]) ? 'on' : 'off'; 896 } 897 $errors[] = [ 898 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], \var_export($setting[1], true)]), 899 'hint' => $l->t('Adjusting this setting in php.ini will make ownCloud run again') 900 ]; 901 $webServerRestart = true; 902 } 903 904 /** 905 * The mbstring.func_overload check can only be performed if the mbstring 906 * module is installed as it will return null if the checking setting is 907 * not available and thus a check on the boolean value fails. 908 * 909 * TODO: Should probably be implemented in the above generic dependency 910 * check somehow in the long-term. 911 */ 912 if ($iniWrapper->getBool('mbstring.func_overload') !== null && 913 $iniWrapper->getBool('mbstring.func_overload') === true) { 914 $errors[] = [ 915 'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]), 916 'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini') 917 ]; 918 } 919 920 if (\function_exists('xml_parser_create') && 921 \version_compare('2.7.0', LIBXML_DOTTED_VERSION) === 1) { 922 $errors[] = [ 923 'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [LIBXML_DOTTED_VERSION]), 924 'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.') 925 ]; 926 } 927 928 if (!self::isAnnotationsWorking()) { 929 $errors[] = [ 930 'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'), 931 'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.') 932 ]; 933 } 934 935 if (!\OC::$CLI && $webServerRestart) { 936 $errors[] = [ 937 'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'), 938 'hint' => $l->t('Please ask your server administrator to restart the web server.') 939 ]; 940 } 941 942 $errors = \array_merge($errors, self::checkDatabaseVersion()); 943 944 // Cache the result of this function 945 \OC::$server->getSession()->set('checkServer_succeeded', \count($errors) == 0); 946 947 return $errors; 948 } 949 950 /** 951 * Check the database version 952 * 953 * @return array errors array 954 */ 955 public static function checkDatabaseVersion() { 956 $l = \OC::$server->getL10N('lib'); 957 $errors = []; 958 $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite'); 959 if ($dbType === 'pgsql') { 960 // check PostgreSQL version 961 try { 962 $result = \OC_DB::executeAudited('SHOW SERVER_VERSION'); 963 $data = $result->fetchRow(); 964 if (isset($data['server_version'])) { 965 $version = $data['server_version']; 966 if (\version_compare($version, '9.0.0', '<')) { 967 $errors[] = [ 968 'error' => $l->t('PostgreSQL >= 9 required'), 969 'hint' => $l->t('Please upgrade your database version') 970 ]; 971 } 972 } 973 } catch (\Doctrine\DBAL\DBALException $e) { 974 $logger = \OC::$server->getLogger(); 975 $logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9'); 976 $logger->logException($e); 977 } 978 } 979 return $errors; 980 } 981 982 /** 983 * Check for correct file permissions of data directory 984 * 985 * @param string $dataDirectory 986 * @return array arrays with error messages and hints 987 */ 988 public static function checkDataDirectoryPermissions($dataDirectory) { 989 $l = \OC::$server->getL10N('lib'); 990 $errors = []; 991 $permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory' 992 . ' cannot be listed by other users.'); 993 $perms = \substr(\decoct(@\fileperms($dataDirectory)), -3); 994 if (\substr($perms, -1) != '0') { 995 \chmod($dataDirectory, 0770); 996 \clearstatcache(); 997 $perms = \substr(\decoct(@\fileperms($dataDirectory)), -3); 998 if (\substr($perms, 2, 1) != '0') { 999 $errors[] = [ 1000 'error' => $l->t('Your Data directory is readable by other users'), 1001 'hint' => $permissionsModHint 1002 ]; 1003 } 1004 } 1005 return $errors; 1006 } 1007 1008 /** 1009 * Check that the data directory exists and is valid by 1010 * checking the existence of the ".ocdata" file. 1011 * 1012 * @param string $dataDirectory data directory path 1013 * @return array errors found 1014 */ 1015 public static function checkDataDirectoryValidity($dataDirectory) { 1016 $l = \OC::$server->getL10N('lib'); 1017 $errors = []; 1018 if ($dataDirectory[0] !== '/') { 1019 $errors[] = [ 1020 'error' => $l->t('Your Data directory must be an absolute path'), 1021 'hint' => $l->t('Check the value of "datadirectory" in your configuration') 1022 ]; 1023 } 1024 if (!\file_exists($dataDirectory . '/.ocdata')) { 1025 $errors[] = [ 1026 'error' => $l->t('Your Data directory is invalid'), 1027 'hint' => $l->t('Please check that the data directory contains a file' . 1028 ' ".ocdata" in its root.') 1029 ]; 1030 } 1031 return $errors; 1032 } 1033 1034 /** 1035 * @return bool 1036 * @throws LoginException 1037 */ 1038 private static function checkPreSignedUrl(): bool { 1039 $request = \Sabre\HTTP\Sapi::createFromServerArray($_SERVER); 1040 $verifier = new Verifier($request, \OC::$server->getConfig()); 1041 if ($verifier->isSignedRequest()) { 1042 if (!$verifier->signedRequestIsValid()) { 1043 throw new LoginException('Invalid pre signed url'); 1044 } 1045 1046 $urlCredential = $verifier->getUrlCredential(); 1047 $user = \OC::$server->getUserManager()->get($urlCredential); 1048 if ($user === null) { 1049 $message = \OC::$server->getL10N('dav')->t('User unknown'); 1050 throw new LoginException($message); 1051 } 1052 if (!$user->isEnabled()) { 1053 $message = \OC::$server->getL10N('dav')->t('User disabled'); 1054 throw new LoginException($message); 1055 } 1056 \OC::$server->getUserSession()->setUser($user); 1057 \OC_Util::setupFS($urlCredential); 1058 \OC::$server->getSession()->close(); 1059 return true; 1060 } 1061 return false; 1062 } 1063 1064 /** 1065 * Check if the user is logged in, redirects to home if not. With 1066 * redirect URL parameter to the request URI. 1067 * 1068 * @param bool $allowPreSigned 1069 * @return void 1070 * @throws LoginException 1071 */ 1072 public static function checkLoggedIn($allowPreSigned = false): void { 1073 if ($allowPreSigned && self::checkPreSignedUrl()) { 1074 return; 1075 } 1076 // Check if we are a user 1077 $userSession = \OC::$server->getUserSession(); 1078 if ($userSession && !$userSession->isLoggedIn()) { 1079 \header( 1080 'Location: ' . \OC::$server->getURLGenerator()->linkToRoute( 1081 'core.login.showLoginForm', 1082 [ 1083 'redirect_url' => \urlencode(\OC::$server->getRequest()->getRequestUri()), 1084 ] 1085 ) 1086 ); 1087 exit(); 1088 } 1089 // Redirect to index page if 2FA challenge was not solved yet 1090 if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor()) { 1091 \header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); 1092 exit(); 1093 } 1094 // Redirect to index page if any IAuthModule check fails 1095 try { 1096 \OC::$server->getAccountModuleManager()->check($userSession->getUser()); 1097 } catch (AccountCheckException $ex) { 1098 \header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); 1099 exit(); 1100 } 1101 } 1102 1103 /** 1104 * Check if the user is a admin, redirects to home if not 1105 * 1106 * @return void 1107 */ 1108 public static function checkAdminUser() { 1109 OC_Util::checkLoggedIn(); 1110 if (!OC_User::isAdminUser(OC_User::getUser())) { 1111 \header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); 1112 exit(); 1113 } 1114 } 1115 1116 /** 1117 * Check if it is allowed to remember login. 1118 * 1119 * @note Every app can set 'rememberlogin' to 'false' to disable the remember login feature 1120 * 1121 * @return bool 1122 */ 1123 public static function rememberLoginAllowed() { 1124 $apps = OC_App::getEnabledApps(); 1125 1126 foreach ($apps as $app) { 1127 $appInfo = OC_App::getAppInfo($app); 1128 if (isset($appInfo['rememberlogin']) && $appInfo['rememberlogin'] === 'false') { 1129 return false; 1130 } 1131 } 1132 return true; 1133 } 1134 1135 /** 1136 * Check if the user is a subadmin, redirects to home if not 1137 * 1138 * @return null|boolean $groups where the current user is subadmin 1139 */ 1140 public static function checkSubAdminUser() { 1141 OC_Util::checkLoggedIn(); 1142 $userObject = \OC::$server->getUserSession()->getUser(); 1143 $isSubAdmin = false; 1144 if ($userObject !== null) { 1145 $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject); 1146 } 1147 1148 if (!$isSubAdmin) { 1149 \header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); 1150 exit(); 1151 } 1152 return true; 1153 } 1154 1155 /** 1156 * Returns the URL of the default page 1157 * based on the system configuration and 1158 * the apps visible for the current user 1159 * 1160 * @return string URL 1161 */ 1162 public static function getDefaultPageUrl() { 1163 $urlGenerator = \OC::$server->getURLGenerator(); 1164 // Deny the redirect if the URL contains a @ 1165 // This prevents unvalidated redirects like ?redirect_url=:user@domain.com 1166 if (isset($_REQUEST['redirect_url']) && \strpos($_REQUEST['redirect_url'], '@') === false) { 1167 $location = $urlGenerator->getAbsoluteURL(\urldecode($_REQUEST['redirect_url'])); 1168 } else { 1169 $defaultPage = \OC::$server->getAppConfig()->getValue('core', 'defaultpage'); 1170 if ($defaultPage) { 1171 $location = $urlGenerator->getAbsoluteURL($defaultPage); 1172 } else { 1173 $appId = 'files'; 1174 $defaultApps = \explode(',', \OC::$server->getConfig()->getSystemValue('defaultapp', 'files')); 1175 // find the first app that is enabled for the current user 1176 foreach ($defaultApps as $defaultApp) { 1177 $defaultApp = OC_App::cleanAppId(\strip_tags($defaultApp)); 1178 if (static::getAppManager()->isEnabledForUser($defaultApp)) { 1179 $appId = $defaultApp; 1180 break; 1181 } 1182 } 1183 1184 if (\getenv('front_controller_active') === 'true') { 1185 $location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/'); 1186 } else { 1187 $location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/'); 1188 } 1189 } 1190 } 1191 return $location; 1192 } 1193 1194 /** 1195 * Redirect to the user default page 1196 * 1197 * @return void 1198 */ 1199 public static function redirectToDefaultPage() { 1200 $location = self::getDefaultPageUrl(); 1201 \header('Location: ' . $location); 1202 exit(); 1203 } 1204 1205 /** 1206 * get an id unique for this instance 1207 * 1208 * @return string 1209 */ 1210 public static function getInstanceId() { 1211 $id = \OC::$server->getSystemConfig()->getValue('instanceid', null); 1212 if ($id === null) { 1213 // We need to guarantee at least one letter in instanceid so it can be used as the session_name 1214 $id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS); 1215 \OC::$server->getSystemConfig()->setValue('instanceid', $id); 1216 } 1217 return $id; 1218 } 1219 1220 /** 1221 * Public function to sanitize HTML 1222 * 1223 * This function is used to sanitize HTML and should be applied on any 1224 * string or array of strings before displaying it on a web page. 1225 * 1226 * @param string|array $value 1227 * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter. 1228 */ 1229 public static function sanitizeHTML($value) { 1230 if (\is_array($value)) { 1231 $value = \array_map(function ($value) { 1232 return self::sanitizeHTML($value); 1233 }, $value); 1234 } else { 1235 // Specify encoding for PHP<5.4 1236 $value = \htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); 1237 } 1238 return $value; 1239 } 1240 1241 /** 1242 * Public function to encode url parameters 1243 * 1244 * This function is used to encode path to file before output. 1245 * Encoding is done according to RFC 3986 with one exception: 1246 * Character '/' is preserved as is. 1247 * 1248 * @param string $component part of URI to encode 1249 * @return string 1250 */ 1251 public static function encodePath($component) { 1252 $encoded = \rawurlencode($component); 1253 $encoded = \str_replace('%2F', '/', $encoded); 1254 return $encoded; 1255 } 1256 1257 public function createHtaccessTestFile(\OCP\IConfig $config) { 1258 // php dev server does not support htaccess 1259 if (\php_sapi_name() === 'cli-server') { 1260 return false; 1261 } 1262 if (\OC::$CLI) { 1263 return false; 1264 } 1265 1266 // testdata 1267 $fileName = '/htaccesstest.txt'; 1268 // test content containing the string "HTACCESSFAIL" 1269 $testContent = 'HTACCESSFAIL: This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.'; 1270 1271 // creating a test file 1272 $testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName; 1273 1274 if (\file_exists($testFile)) {// already running this test, possible recursive call 1275 return false; 1276 } 1277 1278 $fp = @\fopen($testFile, 'w'); 1279 if (!$fp) { 1280 throw new OC\HintException( 1281 'Can\'t create test file to check for working .htaccess file.', 1282 'Make sure it is possible for the webserver to write to ' . $testFile 1283 ); 1284 } 1285 \fwrite($fp, $testContent); 1286 \fclose($fp); 1287 } 1288 1289 /** 1290 * Check if the setlocal call does not work. This can happen if the right 1291 * local packages are not available on the server. 1292 * 1293 * @return bool 1294 */ 1295 public static function isSetLocaleWorking() { 1296 if (\basename('§') === '§') { 1297 return true; 1298 } 1299 1300 \setlocale(LC_ALL, 'C.UTF-8', 'C'); 1301 \setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0'); 1302 1303 return \basename('§') === '§'; 1304 } 1305 1306 /** 1307 * Check if it's possible to get the inline annotations 1308 * 1309 * @return bool 1310 */ 1311 public static function isAnnotationsWorking() { 1312 $reflection = new \ReflectionMethod(__METHOD__); 1313 $docs = $reflection->getDocComment(); 1314 1315 return (\is_string($docs) && \strlen($docs) > 50); 1316 } 1317 1318 /** 1319 * Check if the PHP module fileinfo is loaded. 1320 * 1321 * @return bool 1322 */ 1323 public static function fileInfoLoaded() { 1324 return \function_exists('finfo_open'); 1325 } 1326 1327 /** 1328 * clear all levels of output buffering 1329 * 1330 * @return void 1331 */ 1332 public static function obEnd() { 1333 while (\ob_get_level()) { 1334 \ob_end_clean(); 1335 } 1336 } 1337 1338 /** 1339 * Checks whether the server is running on the given OS type 1340 * 1341 * @param string $osType linux|mac|bsd etc 1342 * @return bool true if running on that OS type, false otherwise 1343 */ 1344 public static function runningOn($osType) { 1345 $osType = \strtolower($osType) === 'mac' ? 'darwin' : \strtolower($osType); 1346 1347 if ($osType === 'bsd') { 1348 return (\strpos(\strtolower(PHP_OS), $osType) !== false); 1349 } else { 1350 return (\strtolower(\substr(PHP_OS, 0, \strlen($osType))) === $osType); 1351 } 1352 } 1353 1354 /** 1355 * Checks whether server is running on HHVM 1356 * 1357 * @return bool True if running on HHVM, false otherwise 1358 */ 1359 public static function runningOnHhvm() { 1360 return \defined('HHVM_VERSION'); 1361 } 1362 1363 /** 1364 * Handles the case that there may not be a theme, then check if a "default" 1365 * theme exists and take that one 1366 * 1367 * @return \OCP\Theme\ITheme the theme 1368 */ 1369 public static function getTheme() { 1370 /** @var \OCP\Theme\IThemeService $themeService */ 1371 $themeService = \OC::$server->query('ThemeService'); 1372 return $themeService->getTheme(); 1373 } 1374 1375 /** 1376 * Clear a single file from the opcode cache 1377 * This is useful for writing to the config file 1378 * in case the opcode cache does not re-validate files 1379 * Returns true if successful, false if unsuccessful: 1380 * caller should fall back on clearing the entire cache 1381 * with clearOpcodeCache() if unsuccessful 1382 * 1383 * @param string $path the path of the file to clear from the cache 1384 * @return bool true if underlying function returns true, otherwise false 1385 */ 1386 public static function deleteFromOpcodeCache($path) { 1387 $ret = false; 1388 if ($path) { 1389 // APC >= 3.1.1 1390 if (\function_exists('apc_delete_file')) { 1391 $ret = @apc_delete_file($path); 1392 } 1393 // Zend OpCache >= 7.0.0, PHP >= 5.5.0 1394 if (\function_exists('opcache_invalidate')) { 1395 $ret = \opcache_invalidate($path); 1396 } 1397 } 1398 return $ret; 1399 } 1400 1401 /** 1402 * Clear the opcode cache if one exists 1403 * This is necessary for writing to the config file 1404 * in case the opcode cache does not re-validate files 1405 * 1406 * @return void 1407 */ 1408 public static function clearOpcodeCache() { 1409 // APC 1410 if (\function_exists('apc_clear_cache')) { 1411 \apc_clear_cache(); 1412 } 1413 // Zend Opcache 1414 if (\function_exists('accelerator_reset')) { 1415 accelerator_reset(); 1416 } 1417 // Opcache (PHP >= 5.5) 1418 if (\function_exists('opcache_reset')) { 1419 \opcache_reset(); 1420 } 1421 } 1422 1423 /** 1424 * Normalize a unicode string 1425 * 1426 * @param string $value a not normalized string 1427 * @return bool|string 1428 */ 1429 public static function normalizeUnicode($value) { 1430 if (Normalizer::isNormalized($value)) { 1431 return $value; 1432 } 1433 1434 $normalizedValue = Normalizer::normalize($value); 1435 if ($normalizedValue === null || $normalizedValue === false) { 1436 \OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']); 1437 return $value; 1438 } 1439 1440 return $normalizedValue; 1441 } 1442 1443 /** 1444 * @param boolean|string $file 1445 * @return string 1446 */ 1447 public static function basename($file) { 1448 $file = \rtrim($file, '/'); 1449 $t = \explode('/', $file); 1450 return \array_pop($t); 1451 } 1452 1453 /** 1454 * A human readable string is generated based on version, channel and build number 1455 * 1456 * @return string 1457 */ 1458 public static function getHumanVersion() { 1459 $version = OC_Util::getVersionString() . ' (' . OC_Util::getChannel() . ')'; 1460 $build = OC_Util::getBuild(); 1461 if (!empty($build) and OC_Util::getChannel() === 'daily') { 1462 $version .= ' Build:' . $build; 1463 } 1464 return $version; 1465 } 1466 1467 /** 1468 * Returns whether the given file name is valid 1469 * 1470 * @param string $file file name to check 1471 * @return bool true if the file name is valid, false otherwise 1472 * @deprecated use \OC\Files\View::verifyPath() 1473 */ 1474 public static function isValidFileName($file) { 1475 $trimmed = \trim($file); 1476 if ($trimmed === '') { 1477 return false; 1478 } 1479 if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { 1480 return false; 1481 } 1482 1483 // detect part files 1484 if (\preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) { 1485 return false; 1486 } 1487 1488 foreach (\str_split($trimmed) as $char) { 1489 if (\strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) { 1490 return false; 1491 } 1492 } 1493 return true; 1494 } 1495 1496 /** 1497 * Check whether the instance needs to perform an upgrade, 1498 * either when the core version is higher or any app requires 1499 * an upgrade. 1500 * 1501 * @param \OCP\IConfig $config 1502 * @return bool whether the core or any app needs an upgrade 1503 * @throws \OC\HintException When the upgrade from the given version is not allowed 1504 */ 1505 public static function needUpgrade(\OCP\IConfig $config) { 1506 if ($config->getSystemValue('installed', false)) { 1507 $installedVersion = $config->getSystemValue('version', '0.0.0'); 1508 $currentVersion = \implode('.', \OCP\Util::getVersion()); 1509 $versionDiff = \version_compare($currentVersion, $installedVersion); 1510 if ($versionDiff > 0) { 1511 return true; 1512 } elseif ($config->getSystemValue('debug', false) && $versionDiff < 0) { 1513 // downgrade with debug 1514 $installedMajor = \explode('.', $installedVersion); 1515 $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; 1516 $currentMajor = \explode('.', $currentVersion); 1517 $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; 1518 if ($installedMajor === $currentMajor) { 1519 // Same major, allow downgrade for developers 1520 return true; 1521 } else { 1522 // downgrade attempt, throw exception 1523 throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); 1524 } 1525 } elseif ($versionDiff < 0) { 1526 // downgrade attempt, throw exception 1527 throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); 1528 } 1529 1530 // also check for upgrades for apps (independently from the user) 1531 $apps = \OC_App::getEnabledApps(false, true); 1532 $shouldUpgrade = false; 1533 foreach ($apps as $app) { 1534 if (\OC_App::shouldUpgrade($app)) { 1535 $shouldUpgrade = true; 1536 break; 1537 } 1538 } 1539 return $shouldUpgrade; 1540 } else { 1541 return false; 1542 } 1543 } 1544} 1545