Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like OC often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OC, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 59 | class OC { |
||
| 60 | /** |
||
| 61 | * Associative array for autoloading. classname => filename |
||
| 62 | */ |
||
| 63 | public static $CLASSPATH = array(); |
||
| 64 | /** |
||
| 65 | * The installation path for Nextcloud on the server (e.g. /srv/http/nextcloud) |
||
| 66 | */ |
||
| 67 | public static $SERVERROOT = ''; |
||
| 68 | /** |
||
| 69 | * the current request path relative to the Nextcloud root (e.g. files/index.php) |
||
| 70 | */ |
||
| 71 | private static $SUBURI = ''; |
||
| 72 | /** |
||
| 73 | * the Nextcloud root path for http requests (e.g. nextcloud/) |
||
| 74 | */ |
||
| 75 | public static $WEBROOT = ''; |
||
| 76 | /** |
||
| 77 | * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and |
||
| 78 | * web path in 'url' |
||
| 79 | */ |
||
| 80 | public static $APPSROOTS = array(); |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @var string |
||
| 84 | */ |
||
| 85 | public static $configDir; |
||
| 86 | |||
| 87 | /** |
||
| 88 | * requested app |
||
| 89 | */ |
||
| 90 | public static $REQUESTEDAPP = ''; |
||
| 91 | |||
| 92 | /** |
||
| 93 | * check if Nextcloud runs in cli mode |
||
| 94 | */ |
||
| 95 | public static $CLI = false; |
||
| 96 | |||
| 97 | /** |
||
| 98 | * @var \OC\Autoloader $loader |
||
| 99 | */ |
||
| 100 | public static $loader = null; |
||
| 101 | |||
| 102 | /** @var \Composer\Autoload\ClassLoader $composerAutoloader */ |
||
| 103 | public static $composerAutoloader = null; |
||
| 104 | |||
| 105 | /** |
||
| 106 | * @var \OC\Server |
||
| 107 | */ |
||
| 108 | public static $server = null; |
||
| 109 | |||
| 110 | /** |
||
| 111 | * @var \OC\Config |
||
| 112 | */ |
||
| 113 | private static $config = null; |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @throws \RuntimeException when the 3rdparty directory is missing or |
||
| 117 | * the app path list is empty or contains an invalid path |
||
| 118 | */ |
||
| 119 | public static function initPaths() { |
||
| 224 | |||
| 225 | public static function checkConfig() { |
||
| 257 | |||
| 258 | public static function checkInstalled() { |
||
| 273 | |||
| 274 | public static function checkMaintenanceMode() { |
||
| 289 | |||
| 290 | public static function checkSingleUserMode($lockIfNoUserLoggedIn = false) { |
||
| 315 | |||
| 316 | /** |
||
| 317 | * Checks if the version requires an update and shows |
||
| 318 | * @param bool $showTemplate Whether an update screen should get shown |
||
| 319 | * @return bool|void |
||
| 320 | */ |
||
| 321 | public static function checkUpgrade($showTemplate = true) { |
||
| 333 | |||
| 334 | /** |
||
| 335 | * Prints the upgrade page |
||
| 336 | */ |
||
| 337 | private static function printUpgradePage() { |
||
| 395 | |||
| 396 | public static function initSession() { |
||
| 442 | |||
| 443 | /** |
||
| 444 | * @return string |
||
| 445 | */ |
||
| 446 | private static function getSessionLifeTime() { |
||
| 449 | |||
| 450 | public static function loadAppClassPaths() { |
||
| 463 | |||
| 464 | /** |
||
| 465 | * Try to set some values to the required Nextcloud default |
||
| 466 | */ |
||
| 467 | public static function setRequiredIniValues() { |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Send the same site cookies |
||
| 474 | */ |
||
| 475 | private static function sendSameSiteCookies() { |
||
| 476 | $cookieParams = session_get_cookie_params(); |
||
| 477 | $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : ''; |
||
| 478 | $policies = [ |
||
| 479 | 'lax', |
||
| 480 | 'strict', |
||
| 481 | ]; |
||
| 482 | foreach($policies as $policy) { |
||
| 483 | header( |
||
| 484 | sprintf( |
||
| 485 | 'Set-Cookie: nc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s', |
||
| 486 | $policy, |
||
| 487 | $cookieParams['path'], |
||
| 488 | $policy |
||
| 489 | ), |
||
| 490 | false |
||
| 491 | ); |
||
| 492 | } |
||
| 493 | } |
||
| 494 | |||
| 495 | /** |
||
| 496 | * Same Site cookie to further mitigate CSRF attacks. This cookie has to |
||
| 497 | * be set in every request if cookies are sent to add a second level of |
||
| 498 | * defense against CSRF. |
||
| 499 | * |
||
| 500 | * If the cookie is not sent this will set the cookie and reload the page. |
||
| 501 | * We use an additional cookie since we want to protect logout CSRF and |
||
| 502 | * also we can't directly interfere with PHP's session mechanism. |
||
| 503 | */ |
||
| 504 | private static function performSameSiteCookieProtection() { |
||
| 505 | if(count($_COOKIE) > 0) { |
||
| 506 | $request = \OC::$server->getRequest(); |
||
| 507 | $requestUri = $request->getScriptName(); |
||
| 508 | $processingScript = explode('/', $requestUri); |
||
| 509 | $processingScript = $processingScript[count($processingScript)-1]; |
||
| 510 | // FIXME: In a SAML scenario we don't get any strict or lax cookie |
||
| 511 | // send for the ACS endpoint. Since we have some legacy code in Nextcloud |
||
| 512 | // (direct PHP files) the enforcement of lax cookies is performed here |
||
| 513 | // instead of the middleware. |
||
| 514 | // |
||
| 515 | // This means we cannot exclude some routes from the cookie validation, |
||
| 516 | // which normally is not a problem but is a little bit cumbersome for |
||
| 517 | // this use-case. |
||
| 518 | // Once the old legacy PHP endpoints have been removed we can move |
||
| 519 | // the verification into a middleware and also adds some exemptions. |
||
| 520 | // |
||
| 521 | // Questions about this code? Ask Lukas ;-) |
||
| 522 | $currentUrl = substr(explode('?',$request->getRequestUri(), 2)[0], strlen(\OC::$WEBROOT)); |
||
| 523 | if($currentUrl === '/index.php/apps/user_saml/saml/acs') { |
||
| 524 | return; |
||
| 525 | } |
||
| 526 | // For the "index.php" endpoint only a lax cookie is required. |
||
| 527 | if($processingScript === 'index.php') { |
||
| 528 | if(!$request->passesLaxCookieCheck()) { |
||
| 529 | self::sendSameSiteCookies(); |
||
| 530 | header('Location: '.$_SERVER['REQUEST_URI']); |
||
| 531 | exit(); |
||
| 532 | } |
||
| 533 | } else { |
||
| 534 | // All other endpoints require the lax and the strict cookie |
||
| 535 | if(!$request->passesStrictCookieCheck()) { |
||
| 536 | self::sendSameSiteCookies(); |
||
| 537 | // Debug mode gets access to the resources without strict cookie |
||
| 538 | // due to the fact that the SabreDAV browser also lives there. |
||
| 539 | if(!\OC::$server->getConfig()->getSystemValue('debug', false)) { |
||
| 540 | http_response_code(\OCP\AppFramework\Http::STATUS_SERVICE_UNAVAILABLE); |
||
| 541 | exit(); |
||
| 542 | } |
||
| 543 | } |
||
| 544 | } |
||
| 545 | } elseif(!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) { |
||
| 546 | self::sendSameSiteCookies(); |
||
| 547 | } |
||
| 548 | } |
||
| 549 | |||
| 550 | public static function init() { |
||
| 551 | // calculate the root directories |
||
| 552 | OC::$SERVERROOT = str_replace("\\", '/', substr(__DIR__, 0, -4)); |
||
| 553 | |||
| 554 | // register autoloader |
||
| 555 | $loaderStart = microtime(true); |
||
| 556 | require_once __DIR__ . '/autoloader.php'; |
||
| 557 | self::$loader = new \OC\Autoloader([ |
||
| 558 | OC::$SERVERROOT . '/lib/private/legacy', |
||
| 559 | ]); |
||
| 560 | if (defined('PHPUNIT_RUN')) { |
||
| 561 | self::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); |
||
| 562 | } |
||
| 563 | spl_autoload_register(array(self::$loader, 'load')); |
||
| 564 | $loaderEnd = microtime(true); |
||
| 565 | |||
| 566 | self::$CLI = (php_sapi_name() == 'cli'); |
||
| 567 | |||
| 568 | // Add default composer PSR-4 autoloader |
||
| 569 | self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; |
||
| 570 | |||
| 571 | try { |
||
| 572 | self::initPaths(); |
||
| 573 | // setup 3rdparty autoloader |
||
| 574 | $vendorAutoLoad = OC::$SERVERROOT. '/3rdparty/autoload.php'; |
||
| 575 | if (!file_exists($vendorAutoLoad)) { |
||
| 576 | throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".'); |
||
| 577 | } |
||
| 578 | require_once $vendorAutoLoad; |
||
| 579 | |||
| 580 | } catch (\RuntimeException $e) { |
||
| 581 | if (!self::$CLI) { |
||
| 582 | $claimedProtocol = strtoupper($_SERVER['SERVER_PROTOCOL']); |
||
| 583 | $protocol = in_array($claimedProtocol, ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2']) ? $claimedProtocol : 'HTTP/1.1'; |
||
| 584 | header($protocol . ' ' . OC_Response::STATUS_SERVICE_UNAVAILABLE); |
||
| 585 | } |
||
| 586 | // we can't use the template error page here, because this needs the |
||
| 587 | // DI container which isn't available yet |
||
| 588 | print($e->getMessage()); |
||
| 589 | exit(); |
||
| 590 | } |
||
| 591 | |||
| 592 | // setup the basic server |
||
| 593 | self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); |
||
| 594 | \OC::$server->getEventLogger()->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd); |
||
| 595 | \OC::$server->getEventLogger()->start('boot', 'Initialize'); |
||
| 596 | |||
| 597 | // Don't display errors and log them |
||
| 598 | error_reporting(E_ALL | E_STRICT); |
||
| 599 | @ini_set('display_errors', 0); |
||
| 600 | @ini_set('log_errors', 1); |
||
| 601 | |||
| 602 | date_default_timezone_set('UTC'); |
||
| 603 | |||
| 604 | //try to configure php to enable big file uploads. |
||
| 605 | //this doesn´t work always depending on the webserver and php configuration. |
||
| 606 | //Let´s try to overwrite some defaults anyway |
||
| 607 | |||
| 608 | //try to set the maximum execution time to 60min |
||
| 609 | @set_time_limit(3600); |
||
| 610 | @ini_set('max_execution_time', 3600); |
||
| 611 | @ini_set('max_input_time', 3600); |
||
| 612 | |||
| 613 | //try to set the maximum filesize to 10G |
||
| 614 | @ini_set('upload_max_filesize', '10G'); |
||
| 615 | @ini_set('post_max_size', '10G'); |
||
| 616 | @ini_set('file_uploads', '50'); |
||
| 617 | |||
| 618 | self::setRequiredIniValues(); |
||
| 619 | self::handleAuthHeaders(); |
||
| 620 | self::registerAutoloaderCache(); |
||
| 621 | |||
| 622 | // initialize intl fallback is necessary |
||
| 623 | \Patchwork\Utf8\Bootup::initIntl(); |
||
| 624 | OC_Util::isSetLocaleWorking(); |
||
| 625 | |||
| 626 | if (!defined('PHPUNIT_RUN')) { |
||
| 627 | OC\Log\ErrorHandler::setLogger(\OC::$server->getLogger()); |
||
| 628 | $debug = \OC::$server->getConfig()->getSystemValue('debug', false); |
||
| 629 | OC\Log\ErrorHandler::register($debug); |
||
| 630 | } |
||
| 631 | |||
| 632 | // register the stream wrappers |
||
| 633 | stream_wrapper_register('fakedir', 'OC\Files\Stream\Dir'); |
||
| 634 | stream_wrapper_register('static', 'OC\Files\Stream\StaticStream'); |
||
| 635 | stream_wrapper_register('close', 'OC\Files\Stream\Close'); |
||
| 636 | stream_wrapper_register('quota', 'OC\Files\Stream\Quota'); |
||
| 637 | stream_wrapper_register('oc', 'OC\Files\Stream\OC'); |
||
| 638 | |||
| 639 | \OC::$server->getEventLogger()->start('init_session', 'Initialize session'); |
||
| 640 | OC_App::loadApps(array('session')); |
||
| 641 | if (!self::$CLI) { |
||
| 642 | self::initSession(); |
||
| 643 | } |
||
| 644 | \OC::$server->getEventLogger()->end('init_session'); |
||
| 645 | self::checkConfig(); |
||
| 646 | self::checkInstalled(); |
||
| 647 | |||
| 648 | OC_Response::addSecurityHeaders(); |
||
| 649 | if(self::$server->getRequest()->getServerProtocol() === 'https') { |
||
| 650 | ini_set('session.cookie_secure', true); |
||
| 651 | } |
||
| 652 | |||
| 653 | self::performSameSiteCookieProtection(); |
||
| 654 | |||
| 655 | if (!defined('OC_CONSOLE')) { |
||
| 656 | $errors = OC_Util::checkServer(\OC::$server->getConfig()); |
||
| 657 | if (count($errors) > 0) { |
||
| 658 | if (self::$CLI) { |
||
| 659 | // Convert l10n string into regular string for usage in database |
||
| 660 | $staticErrors = []; |
||
| 661 | foreach ($errors as $error) { |
||
| 662 | echo $error['error'] . "\n"; |
||
| 663 | echo $error['hint'] . "\n\n"; |
||
| 664 | $staticErrors[] = [ |
||
| 665 | 'error' => (string)$error['error'], |
||
| 666 | 'hint' => (string)$error['hint'], |
||
| 667 | ]; |
||
| 668 | } |
||
| 669 | |||
| 670 | try { |
||
| 671 | \OC::$server->getConfig()->setAppValue('core', 'cronErrors', json_encode($staticErrors)); |
||
| 672 | } catch (\Exception $e) { |
||
| 673 | echo('Writing to database failed'); |
||
| 674 | } |
||
| 675 | exit(1); |
||
| 676 | } else { |
||
| 677 | OC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE); |
||
| 678 | OC_Template::printGuestPage('', 'error', array('errors' => $errors)); |
||
| 679 | exit; |
||
| 680 | } |
||
| 681 | View Code Duplication | } elseif (self::$CLI && \OC::$server->getConfig()->getSystemValue('installed', false)) { |
|
| 682 | \OC::$server->getConfig()->deleteAppValue('core', 'cronErrors'); |
||
| 683 | } |
||
| 684 | } |
||
| 685 | //try to set the session lifetime |
||
| 686 | $sessionLifeTime = self::getSessionLifeTime(); |
||
| 687 | @ini_set('gc_maxlifetime', (string)$sessionLifeTime); |
||
| 688 | |||
| 689 | $systemConfig = \OC::$server->getSystemConfig(); |
||
| 690 | |||
| 691 | // User and Groups |
||
| 692 | if (!$systemConfig->getValue("installed", false)) { |
||
| 693 | self::$server->getSession()->set('user_id', ''); |
||
| 694 | } |
||
| 695 | |||
| 696 | OC_User::useBackend(new \OC\User\Database()); |
||
| 697 | OC_Group::useBackend(new \OC\Group\Database()); |
||
| 698 | |||
| 699 | // Subscribe to the hook |
||
| 700 | \OCP\Util::connectHook( |
||
| 701 | '\OCA\Files_Sharing\API\Server2Server', |
||
| 702 | 'preLoginNameUsedAsUserName', |
||
| 703 | '\OC\User\Database', |
||
| 704 | 'preLoginNameUsedAsUserName' |
||
| 705 | ); |
||
| 706 | |||
| 707 | //setup extra user backends |
||
| 708 | if (!self::checkUpgrade(false)) { |
||
| 709 | OC_User::setupBackends(); |
||
| 710 | } else { |
||
| 711 | // Run upgrades in incognito mode |
||
| 712 | OC_User::setIncognitoMode(true); |
||
| 713 | } |
||
| 714 | |||
| 715 | self::registerCacheHooks(); |
||
| 716 | self::registerFilesystemHooks(); |
||
| 717 | if ($systemConfig->getValue('enable_previews', true)) { |
||
| 718 | self::registerPreviewHooks(); |
||
| 719 | } |
||
| 720 | self::registerShareHooks(); |
||
| 721 | self::registerLogRotate(); |
||
| 722 | self::registerEncryptionWrapper(); |
||
| 723 | self::registerEncryptionHooks(); |
||
| 724 | |||
| 725 | //make sure temporary files are cleaned up |
||
| 726 | $tmpManager = \OC::$server->getTempManager(); |
||
| 727 | register_shutdown_function(array($tmpManager, 'clean')); |
||
| 728 | $lockProvider = \OC::$server->getLockingProvider(); |
||
| 729 | register_shutdown_function(array($lockProvider, 'releaseAll')); |
||
| 730 | |||
| 731 | // Check whether the sample configuration has been copied |
||
| 732 | if($systemConfig->getValue('copied_sample_config', false)) { |
||
| 733 | $l = \OC::$server->getL10N('lib'); |
||
| 734 | header('HTTP/1.1 503 Service Temporarily Unavailable'); |
||
| 735 | header('Status: 503 Service Temporarily Unavailable'); |
||
| 736 | OC_Template::printErrorPage( |
||
| 737 | $l->t('Sample configuration detected'), |
||
| 738 | $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php') |
||
| 739 | ); |
||
| 740 | return; |
||
| 741 | } |
||
| 742 | |||
| 743 | $request = \OC::$server->getRequest(); |
||
| 744 | $host = $request->getInsecureServerHost(); |
||
| 745 | /** |
||
| 746 | * if the host passed in headers isn't trusted |
||
| 747 | * FIXME: Should not be in here at all :see_no_evil: |
||
| 748 | */ |
||
| 749 | if (!OC::$CLI |
||
| 750 | // overwritehost is always trusted, workaround to not have to make |
||
| 751 | // \OC\AppFramework\Http\Request::getOverwriteHost public |
||
| 752 | && self::$server->getConfig()->getSystemValue('overwritehost') === '' |
||
| 753 | && !\OC::$server->getTrustedDomainHelper()->isTrustedDomain($host) |
||
| 754 | && self::$server->getConfig()->getSystemValue('installed', false) |
||
| 755 | ) { |
||
| 756 | header('HTTP/1.1 400 Bad Request'); |
||
| 757 | header('Status: 400 Bad Request'); |
||
| 758 | |||
| 759 | \OC::$server->getLogger()->warning( |
||
| 760 | 'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.', |
||
| 761 | [ |
||
| 762 | 'app' => 'core', |
||
| 763 | 'remoteAddress' => $request->getRemoteAddress(), |
||
| 764 | 'host' => $host, |
||
| 765 | ] |
||
| 766 | ); |
||
| 767 | |||
| 768 | $tmpl = new OCP\Template('core', 'untrustedDomain', 'guest'); |
||
| 769 | $tmpl->assign('domain', $host); |
||
| 770 | $tmpl->printPage(); |
||
| 771 | |||
| 772 | exit(); |
||
| 773 | } |
||
| 774 | \OC::$server->getEventLogger()->end('boot'); |
||
| 775 | } |
||
| 776 | |||
| 777 | /** |
||
| 778 | * register hooks for the cache |
||
| 779 | */ |
||
| 780 | public static function registerCacheHooks() { |
||
| 801 | |||
| 802 | private static function registerEncryptionWrapper() { |
||
| 806 | |||
| 807 | private static function registerEncryptionHooks() { |
||
| 816 | |||
| 817 | /** |
||
| 818 | * register hooks for the cache |
||
| 819 | */ |
||
| 820 | public static function registerLogRotate() { |
||
| 828 | |||
| 829 | /** |
||
| 830 | * register hooks for the filesystem |
||
| 831 | */ |
||
| 832 | public static function registerFilesystemHooks() { |
||
| 837 | |||
| 838 | /** |
||
| 839 | * register hooks for previews |
||
| 840 | */ |
||
| 841 | public static function registerPreviewHooks() { |
||
| 851 | |||
| 852 | /** |
||
| 853 | * register hooks for sharing |
||
| 854 | */ |
||
| 855 | public static function registerShareHooks() { |
||
| 862 | |||
| 863 | protected static function registerAutoloaderCache() { |
||
| 879 | |||
| 880 | /** |
||
| 881 | * Handle the request |
||
| 882 | */ |
||
| 883 | public static function handleRequest() { |
||
| 981 | |||
| 982 | /** |
||
| 983 | * Check login: apache auth, auth token, basic auth |
||
| 984 | * |
||
| 985 | * @param OCP\IRequest $request |
||
| 986 | * @return boolean |
||
| 987 | */ |
||
| 988 | static function handleLogin(OCP\IRequest $request) { |
||
| 1001 | |||
| 1002 | protected static function handleAuthHeaders() { |
||
| 1022 | } |
||
| 1023 | |||
| 1025 |
If you suppress an error, we recommend checking for the error condition explicitly: