diff --git a/Caddyfile b/Caddyfile
new file mode 100644
index 0000000000000..b18b31297838c
--- /dev/null
+++ b/Caddyfile
@@ -0,0 +1,49 @@
+localhost {
+ php_server {
+ worker index.php
+ }
+
+ log {
+ level ERROR
+ output stderr
+ }
+
+ encode gzip
+
+ redir /.well-known/carddav /remote.php/dav 301
+ redir /.well-known/caldav /remote.php/dav 301
+
+ # Rule: Maps most RFC 8615 compliant well-known URIs to our main frontend controller (/index.php) by default
+ @wellKnown {
+ path "/.well-known/"
+ not {
+ path /.well-known/acme-challenge
+ path /.well-known/pki-validation
+ }
+ }
+ rewrite @wellKnown /index.php
+
+ rewrite /ocm-provider/ /index.php
+
+ @forbidden {
+ path /.htaccess
+ path /data/*
+ path /config/*
+ path /db_structure
+ path /.xml
+ path /README
+ path /3rdparty/*
+ path /lib/*
+ path /templates/*
+ path /occ
+ path /build
+ path /tests
+ path /console.php
+ path /autotest
+ path /issue
+ path /indi
+ path /db_
+ path /console
+ }
+ respond @forbidden 404
+}
diff --git a/apps/testing/appinfo/info.xml b/apps/testing/appinfo/info.xml
index d69f902993373..c2dd9a2f5ee93 100644
--- a/apps/testing/appinfo/info.xml
+++ b/apps/testing/appinfo/info.xml
@@ -16,6 +16,11 @@
+
+
+ OCA\Testing\Command\StaticHunt
+
+
monitoring
https://github.com/nextcloud/server/issues
diff --git a/apps/testing/composer/composer/autoload_classmap.php b/apps/testing/composer/composer/autoload_classmap.php
index ad50a15224a02..ee7424a0ea809 100644
--- a/apps/testing/composer/composer/autoload_classmap.php
+++ b/apps/testing/composer/composer/autoload_classmap.php
@@ -9,6 +9,7 @@
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\Testing\\AlternativeHomeUserBackend' => $baseDir . '/../lib/AlternativeHomeUserBackend.php',
'OCA\\Testing\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
+ 'OCA\\Testing\\Command\\StaticHunt' => $baseDir . '/../lib/Command/StaticHunt.php',
'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php',
'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php',
'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.php',
diff --git a/apps/testing/composer/composer/autoload_static.php b/apps/testing/composer/composer/autoload_static.php
index d3e37546416a3..9ea8e6dc0a8f0 100644
--- a/apps/testing/composer/composer/autoload_static.php
+++ b/apps/testing/composer/composer/autoload_static.php
@@ -24,6 +24,7 @@ class ComposerStaticInitTesting
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\Testing\\AlternativeHomeUserBackend' => __DIR__ . '/..' . '/../lib/AlternativeHomeUserBackend.php',
'OCA\\Testing\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
+ 'OCA\\Testing\\Command\\StaticHunt' => __DIR__ . '/..' . '/../lib/Command/StaticHunt.php',
'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php',
'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php',
'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.php',
diff --git a/apps/testing/lib/Command/StaticHunt.php b/apps/testing/lib/Command/StaticHunt.php
new file mode 100644
index 0000000000000..2ee6f9b5d9fbe
--- /dev/null
+++ b/apps/testing/lib/Command/StaticHunt.php
@@ -0,0 +1,110 @@
+setName('testing:static-hunt')
+ ->setDescription('Hunt for static properties in classes');
+ }
+
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $folders = [
+ '' => __DIR__ . '/../../../../lib/private/legacy',
+ '\\OC' => __DIR__ . '/../../../../lib/private',
+ '\\OC\\Core' => __DIR__ . '/../../../../core',
+ ];
+ $apps = $this->appManager->getAllAppsInAppsFolders();
+ foreach ($apps as $app) {
+ $info = $this->appManager->getAppInfo($app);
+ if (!isset($info['namespace'])) {
+ continue;
+ }
+ $folders['\\OCA\\' . $info['namespace']] = $this->appManager->getAppPath($app) . '/lib';
+ }
+ $stats = [
+ 'classes' => 0,
+ 'properties' => 0,
+ ];
+ foreach ($folders as $namespace => $folder) {
+ $this->scanFolder($folder, $namespace, $output, $stats);
+ }
+
+ $output->writeln('Found ' . $stats['properties'] . ' static properties spread among ' . $stats['classes'] . ' classes');
+
+ return 0;
+ }
+
+ private function scanFolder(string $folder, string $namespace, OutputInterface $output, array &$stats): void {
+ $folder = realpath($folder);
+ $output->writeln('Folder ' . $folder, OutputInterface::VERBOSITY_VERBOSE);
+ foreach ($this->recursiveGlob($folder) as $filename) {
+ try {
+ $filename = realpath($filename);
+ if (($namespace === '\\OC') && str_contains($filename, 'lib/private/legacy')) {
+ // Skip legacy in OC as it’s scanned with an empty namespace separately
+ continue;
+ }
+ foreach (self::SKIP_REGEX as $skipRegex) {
+ if (preg_match($skipRegex, $filename)) {
+ continue 2;
+ }
+ }
+ $classname = $namespace . substr(str_replace('/', '\\', substr($filename, strlen($folder))), 0, -4);
+ $output->writeln('Class ' . $classname, OutputInterface::VERBOSITY_VERBOSE);
+ if (!class_exists($classname)) {
+ continue;
+ }
+ $rClass = new \ReflectionClass($classname);
+ $staticProperties = $rClass->getStaticProperties();
+ if (empty($staticProperties)) {
+ continue;
+ }
+ $stats['classes']++;
+ $output->writeln('# ' . str_replace(\OC::$SERVERROOT, '', $filename) . " $classname");
+ foreach ($staticProperties as $property => $value) {
+ $propertyObject = $rClass->getProperty($property);
+ $stats['properties']++;
+ $output->write("$propertyObject");
+ }
+ $output->writeln('');
+ } catch (\Throwable $t) {
+ $output->writeln("$t");
+ }
+ }
+ }
+
+ private function recursiveGlob(string $path, int $depth = 1): \Generator {
+ $pattern = $path . str_repeat('/*', $depth);
+ yield from glob($pattern . '.php');
+ if (!empty(glob($pattern, GLOB_ONLYDIR))) {
+ yield from $this->recursiveGlob($path, $depth + 1);
+ }
+ }
+}
diff --git a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php
index 7dd6b3949e2d2..24621ba2ee202 100644
--- a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php
+++ b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php
@@ -17,7 +17,7 @@
use OCP\Security\ISecureRandom;
class BackupCodeStorage {
- private static $CODE_LENGTH = 16;
+ private const CODE_LENGTH = 16;
public function __construct(
private BackupCodeMapper $mapper,
@@ -40,7 +40,7 @@ public function createCodes(IUser $user, int $number = 10): array {
$uid = $user->getUID();
foreach (range(1, min([$number, 20])) as $i) {
- $code = $this->random->generate(self::$CODE_LENGTH, ISecureRandom::CHAR_HUMAN_READABLE);
+ $code = $this->random->generate(self::CODE_LENGTH, ISecureRandom::CHAR_HUMAN_READABLE);
$dbCode = new BackupCode();
$dbCode->setUserId($uid);
diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php
index b4a5b84720421..42eaa464d5f7a 100644
--- a/apps/user_ldap/lib/Configuration.php
+++ b/apps/user_ldap/lib/Configuration.php
@@ -571,7 +571,7 @@ public function getDefaults(): array {
*/
public function getConfigTranslationArray(): array {
//TODO: merge them into one representation
- static $array = [
+ return [
'ldap_host' => 'ldapHost',
'ldap_port' => 'ldapPort',
'ldap_backup_host' => 'ldapBackupHost',
@@ -644,7 +644,6 @@ public function getConfigTranslationArray(): array {
'ldap_attr_anniversarydate' => 'ldapAttributeAnniversaryDate',
'ldap_attr_pronouns' => 'ldapAttributePronouns',
];
- return $array;
}
/**
diff --git a/apps/user_ldap/lib/Wizard.php b/apps/user_ldap/lib/Wizard.php
index fa77fea8fa2bb..876f001fb2728 100644
--- a/apps/user_ldap/lib/Wizard.php
+++ b/apps/user_ldap/lib/Wizard.php
@@ -16,7 +16,7 @@
use Psr\Log\LoggerInterface;
class Wizard extends LDAPUtility {
- protected static ?IL10N $l = null;
+ private IL10N $l;
protected ?\LDAP\Connection $cr = null;
protected WizardResult $result;
protected LoggerInterface $logger;
@@ -40,9 +40,7 @@ public function __construct(
protected Access $access,
) {
parent::__construct($ldap);
- if (is_null(static::$l)) {
- static::$l = Server::get(IL10NFactory::class)->get('user_ldap');
- }
+ $this->l = Server::get(IL10NFactory::class)->get('user_ldap');
$this->result = new WizardResult();
$this->logger = Server::get(LoggerInterface::class);
}
@@ -94,7 +92,7 @@ public function countGroups() {
$filter = $this->configuration->ldapGroupFilter;
if (empty($filter)) {
- $output = self::$l->n('%n group found', '%n groups found', 0);
+ $output = $this->l->n('%n group found', '%n groups found', 0);
$this->result->addChange('ldap_group_count', $output);
return $this->result;
}
@@ -110,9 +108,9 @@ public function countGroups() {
}
if ($groupsTotal > 1000) {
- $output = self::$l->t('> 1000 groups found');
+ $output = $this->l->t('> 1000 groups found');
} else {
- $output = self::$l->n(
+ $output = $this->l->n(
'%n group found',
'%n groups found',
$groupsTotal
@@ -130,9 +128,9 @@ public function countUsers(): WizardResult {
$usersTotal = $this->countEntries($filter, 'users');
if ($usersTotal > 1000) {
- $output = self::$l->t('> 1000 users found');
+ $output = $this->l->t('> 1000 users found');
} else {
- $output = self::$l->n(
+ $output = $this->l->n(
'%n user found',
'%n users found',
$usersTotal
@@ -216,7 +214,7 @@ public function detectUserDisplayNameAttribute() {
}
}
- throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
+ throw new \Exception($this->l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
}
/**
@@ -431,7 +429,7 @@ public function fetchGroups(string $dbKey, string $confKey): array {
natsort($groupNames);
$this->result->addOptions($dbKey, array_values($groupNames));
} else {
- throw new \Exception(self::$l->t('Could not find the desired feature'));
+ throw new \Exception($this->l->t('Could not find the desired feature'));
}
$setFeatures = $this->configuration->$confKey;
@@ -1024,7 +1022,7 @@ private function connectAndBind(int $port, bool $tls): bool {
$host = $this->configuration->ldapHost;
$hostInfo = parse_url((string)$host);
if (!is_string($host) || !$hostInfo) {
- throw new \Exception(self::$l->t('Invalid Host'));
+ throw new \Exception($this->l->t('Invalid Host'));
}
$this->logger->debug(
'Wiz: Attempting to connect',
@@ -1032,7 +1030,7 @@ private function connectAndBind(int $port, bool $tls): bool {
);
$cr = $this->ldap->connect($host, (string)$port);
if (!$this->ldap->isResource($cr)) {
- throw new \Exception(self::$l->t('Invalid Host'));
+ throw new \Exception($this->l->t('Invalid Host'));
}
/** @var \LDAP\Connection $cr */
@@ -1219,7 +1217,7 @@ private function determineFeature(array $objectclasses, string $attr, string $db
//sorting in the web UI. Therefore: array_values
$this->result->addOptions($dbkey, array_values($availableFeatures));
} else {
- throw new \Exception(self::$l->t('Could not find the desired feature'));
+ throw new \Exception($this->l->t('Could not find the desired feature'));
}
$setFeatures = $this->configuration->$confkey;
@@ -1307,7 +1305,7 @@ private function getConnection(): \LDAP\Connection|false {
* @return array
*/
private function getDefaultLdapPortSettings(): array {
- static $settings = [
+ return [
['port' => 7636, 'tls' => false],
['port' => 636, 'tls' => false],
['port' => 7389, 'tls' => true],
@@ -1315,7 +1313,6 @@ private function getDefaultLdapPortSettings(): array {
['port' => 7389, 'tls' => false],
['port' => 389, 'tls' => false],
];
- return $settings;
}
/**
diff --git a/apps/workflowengine/lib/Command/Index.php b/apps/workflowengine/lib/Command/Index.php
index 1fb8cb416b0aa..c38d19a087ede 100644
--- a/apps/workflowengine/lib/Command/Index.php
+++ b/apps/workflowengine/lib/Command/Index.php
@@ -6,6 +6,7 @@
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
namespace OCA\WorkflowEngine\Command;
use OCA\WorkflowEngine\Helper\ScopeContext;
@@ -43,11 +44,11 @@ protected function configure() {
}
protected function mappedScope(string $scope): int {
- static $scopes = [
+ return match($scope) {
'admin' => IManager::SCOPE_ADMIN,
'user' => IManager::SCOPE_USER,
- ];
- return $scopes[$scope] ?? -1;
+ default => -1,
+ };
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php
index e426f96ac4aeb..a0f479c437092 100644
--- a/apps/workflowengine/lib/Manager.php
+++ b/apps/workflowengine/lib/Manager.php
@@ -65,6 +65,9 @@ class Manager implements IManager {
/** @var CappedMemoryCache */
protected CappedMemoryCache $operationsByScope;
+ /** @var array, ScopeContext[]> $scopesByOperation */
+ private array $scopesByOperation = [];
+
public function __construct(
protected readonly IDBConnection $connection,
protected readonly ContainerInterface $container,
@@ -128,10 +131,8 @@ public function getAllConfiguredEvents() {
* @return ScopeContext[]
*/
public function getAllConfiguredScopesForOperation(string $operationClass): array {
- /** @var array, ScopeContext[]> $scopesByOperation */
- static $scopesByOperation = [];
- if (isset($scopesByOperation[$operationClass])) {
- return $scopesByOperation[$operationClass];
+ if (isset($this->scopesByOperation[$operationClass])) {
+ return $this->scopesByOperation[$operationClass];
}
try {
@@ -152,7 +153,7 @@ public function getAllConfiguredScopesForOperation(string $operationClass): arra
$query->setParameters(['operationClass' => $operationClass]);
$result = $query->executeQuery();
- $scopesByOperation[$operationClass] = [];
+ $this->scopesByOperation[$operationClass] = [];
while ($row = $result->fetchAssociative()) {
$scope = new ScopeContext($row['type'], $row['value']);
@@ -160,10 +161,10 @@ public function getAllConfiguredScopesForOperation(string $operationClass): arra
continue;
}
- $scopesByOperation[$operationClass][$scope->getHash()] = $scope;
+ $this->scopesByOperation[$operationClass][$scope->getHash()] = $scope;
}
- return $scopesByOperation[$operationClass];
+ return $this->scopesByOperation[$operationClass];
}
public function getAllOperations(ScopeContext $scopeContext): array {
diff --git a/build/psalm/StaticVarsChecker.php b/build/psalm/StaticVarsChecker.php
new file mode 100644
index 0000000000000..24d08b45aa8cf
--- /dev/null
+++ b/build/psalm/StaticVarsChecker.php
@@ -0,0 +1,54 @@
+getStmt();
+ $statementsSource = $event->getStatementsSource();
+
+ foreach ($classLike->stmts as $stmt) {
+ if ($stmt instanceof Property) {
+ if ($stmt->isStatic()) {
+ IssueBuffer::maybeAdd(
+ // ImpureStaticProperty is close enough, all static properties are impure to my eyes
+ new \Psalm\Issue\ImpureStaticProperty(
+ 'Static property should not be used as they do not follow requests lifecycle',
+ new CodeLocation($statementsSource, $stmt),
+ )
+ );
+ }
+ }
+ }
+ }
+
+ public static function afterStatementAnalysis(AfterStatementAnalysisEvent $event): ?bool {
+ $stmt = $event->getStmt();
+ if ($stmt instanceof PhpParser\Node\Stmt\Static_) {
+ IssueBuffer::maybeAdd(
+ // Same logic
+ new \Psalm\Issue\ImpureStaticVariable(
+ 'Static var should not be used as they do not follow requests lifecycle and are hard to reset',
+ new CodeLocation($event->getStatementsSource(), $stmt),
+ )
+ );
+ }
+ return null;
+ }
+}
diff --git a/build/psalm/StaticVarsTest.php b/build/psalm/StaticVarsTest.php
new file mode 100644
index 0000000000000..f40ac56ff5144
--- /dev/null
+++ b/build/psalm/StaticVarsTest.php
@@ -0,0 +1,23 @@
+getMigrationsDirectory();
$this->ensureMigrationDirExists($dir);
diff --git a/index.php b/index.php
index b368462371d40..3269ec55ff038 100644
--- a/index.php
+++ b/index.php
@@ -19,90 +19,127 @@
use OCP\Template\ITemplateManager;
use Psr\Log\LoggerInterface;
-try {
- require_once __DIR__ . '/lib/base.php';
+require_once __DIR__ . '/lib/OC.php';
- OC::handleRequest();
-} catch (ServiceUnavailableException $ex) {
- Server::get(LoggerInterface::class)->error($ex->getMessage(), [
- 'app' => 'index',
- 'exception' => $ex,
- ]);
+\OC::boot();
- //show the user a detailed error page
- Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503);
-} catch (HintException $ex) {
+function cleanupStaticCrap() {
+ // FIXME needed because these use a static var
+ \OC_Hook::clear();
+ \OC_Util::$styles = [];
+ \OC_Util::$headers = [];
+ \OC_User::setIncognitoMode(false);
+ \OC_User::$_setupedBackends = [];
+ \OC_App::reset();
+ \OC_Helper::reset();
+}
+
+$handler = static function () {
try {
- Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503);
- } catch (Exception $ex2) {
+ // In worker mode, script name is empty in FrankenPHP
+ if ($_SERVER['SCRIPT_NAME'] === '') {
+ $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
+ }
+ cleanupStaticCrap();
+ OC::init();
+ OC::handleRequest();
+ } catch (ServiceUnavailableException $ex) {
+ Server::get(LoggerInterface::class)->error($ex->getMessage(), [
+ 'app' => 'index',
+ 'exception' => $ex,
+ ]);
+
+ //show the user a detailed error page
+ Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503);
+ } catch (HintException $ex) {
+ try {
+ Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503);
+ } catch (Exception $ex2) {
+ try {
+ Server::get(LoggerInterface::class)->error($ex->getMessage(), [
+ 'app' => 'index',
+ 'exception' => $ex,
+ ]);
+ Server::get(LoggerInterface::class)->error($ex2->getMessage(), [
+ 'app' => 'index',
+ 'exception' => $ex2,
+ ]);
+ } catch (Throwable $e) {
+ // no way to log it properly - but to avoid a white page of death we try harder and ignore this one here
+ }
+
+ //show the user a detailed error page
+ Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
+ }
+ } catch (LoginException $ex) {
+ $request = Server::get(IRequest::class);
+ /**
+ * Routes with the @CORS annotation and other API endpoints should
+ * not return a webpage, so we only print the error page when html is accepted,
+ * otherwise we reply with a JSON array like the SecurityMiddleware would do.
+ */
+ if (stripos($request->getHeader('Accept'), 'html') === false) {
+ http_response_code(401);
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(['message' => $ex->getMessage()]);
+ exit();
+ }
+ Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401);
+ } catch (MaxDelayReached $ex) {
+ $request = Server::get(IRequest::class);
+ /**
+ * Routes with the @CORS annotation and other API endpoints should
+ * not return a webpage, so we only print the error page when html is accepted,
+ * otherwise we reply with a JSON array like the BruteForceMiddleware would do.
+ */
+ if (stripos($request->getHeader('Accept'), 'html') === false) {
+ http_response_code(429);
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(['message' => $ex->getMessage()]);
+ exit();
+ }
+ http_response_code(429);
+ Server::get(ITemplateManager::class)->printGuestPage('core', '429');
+ } catch (Exception $ex) {
+ Server::get(LoggerInterface::class)->error($ex->getMessage(), [
+ 'app' => 'index',
+ 'exception' => $ex,
+ ]);
+
+ //show the user a detailed error page
+ Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
+ } catch (Error $ex) {
try {
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
'app' => 'index',
'exception' => $ex,
]);
- Server::get(LoggerInterface::class)->error($ex2->getMessage(), [
- 'app' => 'index',
- 'exception' => $ex2,
- ]);
- } catch (Throwable $e) {
- // no way to log it properly - but to avoid a white page of death we try harder and ignore this one here
- }
+ } catch (Error $e) {
+ http_response_code(500);
+ header('Content-Type: text/plain; charset=utf-8');
+ print("Internal Server Error\n\n");
+ print("The server encountered an internal error and was unable to complete your request.\n");
+ print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
+ print("More details can be found in the webserver log.\n");
- //show the user a detailed error page
+ throw $ex;
+ }
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
}
-} catch (LoginException $ex) {
- $request = Server::get(IRequest::class);
- /**
- * Routes with the @CORS annotation and other API endpoints should
- * not return a webpage, so we only print the error page when html is accepted,
- * otherwise we reply with a JSON array like the SecurityMiddleware would do.
- */
- if (stripos($request->getHeader('Accept'), 'html') === false) {
- http_response_code(401);
- header('Content-Type: application/json; charset=utf-8');
- echo json_encode(['message' => $ex->getMessage()]);
- exit();
- }
- Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401);
-} catch (MaxDelayReached $ex) {
- $request = Server::get(IRequest::class);
- /**
- * Routes with the @CORS annotation and other API endpoints should
- * not return a webpage, so we only print the error page when html is accepted,
- * otherwise we reply with a JSON array like the BruteForceMiddleware would do.
- */
- if (stripos($request->getHeader('Accept'), 'html') === false) {
- http_response_code(429);
- header('Content-Type: application/json; charset=utf-8');
- echo json_encode(['message' => $ex->getMessage()]);
- exit();
- }
- http_response_code(429);
- Server::get(ITemplateManager::class)->printGuestPage('core', '429');
-} catch (Exception $ex) {
- Server::get(LoggerInterface::class)->error($ex->getMessage(), [
- 'app' => 'index',
- 'exception' => $ex,
- ]);
+};
- //show the user a detailed error page
- Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
-} catch (Error $ex) {
- try {
- Server::get(LoggerInterface::class)->error($ex->getMessage(), [
- 'app' => 'index',
- 'exception' => $ex,
- ]);
- } catch (Error $e) {
- http_response_code(500);
- header('Content-Type: text/plain; charset=utf-8');
- print("Internal Server Error\n\n");
- print("The server encountered an internal error and was unable to complete your request.\n");
- print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
- print("More details can be found in the webserver log.\n");
+if (function_exists('frankenphp_handle_request')) {
+ $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
+ for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
+ $keepRunning = \frankenphp_handle_request($handler);
- throw $ex;
+ // Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
+ gc_collect_cycles();
+
+ if (!$keepRunning) {
+ break;
+ }
}
- Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
+} else {
+ $handler();
}
diff --git a/lib/OC.php b/lib/OC.php
new file mode 100644
index 0000000000000..488a0cf410548
--- /dev/null
+++ b/lib/OC.php
@@ -0,0 +1,1299 @@
+ [
+ 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
+ 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
+ ],
+ ];
+ if (isset($_SERVER['REMOTE_ADDR'])) {
+ $params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
+ }
+ $fakeRequest = new \OC\AppFramework\Http\Request(
+ $params,
+ new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
+ new \OC\AllConfig(new \OC\SystemConfig(self::$config))
+ );
+ $scriptName = $fakeRequest->getScriptName();
+ if (substr($scriptName, -1) == '/') {
+ $scriptName .= 'index.php';
+ //make sure suburi follows the same rules as scriptName
+ if (substr(OC::$SUBURI, -9) != 'index.php') {
+ if (substr(OC::$SUBURI, -1) != '/') {
+ OC::$SUBURI = OC::$SUBURI . '/';
+ }
+ OC::$SUBURI = OC::$SUBURI . 'index.php';
+ }
+ }
+
+ if (OC::$CLI) {
+ OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
+ } else {
+ if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
+ OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
+
+ if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
+ OC::$WEBROOT = '/' . OC::$WEBROOT;
+ }
+ } else {
+ // The scriptName is not ending with OC::$SUBURI
+ // This most likely means that we are calling from CLI.
+ // However some cron jobs still need to generate
+ // a web URL, so we use overwritewebroot as a fallback.
+ OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
+ }
+
+ // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
+ // slash which is required by URL generation.
+ if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
+ && substr($_SERVER['REQUEST_URI'], -1) !== '/') {
+ header('Location: ' . \OC::$WEBROOT . '/');
+ exit();
+ }
+ }
+
+ // search the apps folder
+ $config_paths = self::$config->getValue('apps_paths', []);
+ if (!empty($config_paths)) {
+ foreach ($config_paths as $paths) {
+ if (isset($paths['url']) && isset($paths['path'])) {
+ $paths['url'] = rtrim($paths['url'], '/');
+ $paths['path'] = rtrim($paths['path'], '/');
+ OC::$APPSROOTS[] = $paths;
+ }
+ }
+ } elseif (file_exists(OC::$SERVERROOT . '/apps')) {
+ OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
+ }
+
+
+ if (empty(OC::$APPSROOTS)) {
+ throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
+ . '. You can also configure the location in the config.php file.');
+ }
+ $paths = [];
+ foreach (OC::$APPSROOTS as $path) {
+ $paths[] = $path['path'];
+ if (!is_dir($path['path'])) {
+ throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
+ . ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
+ }
+ }
+
+ // set the right include path
+ set_include_path(
+ implode(PATH_SEPARATOR, $paths)
+ );
+ }
+
+ public static function checkConfig(): void {
+ // Create config if it does not already exist
+ $configFilePath = self::$configDir . '/config.php';
+ if (!file_exists($configFilePath)) {
+ @touch($configFilePath);
+ }
+
+ // Check if config is writable
+ $configFileWritable = is_writable($configFilePath);
+ $configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
+ if (!$configFileWritable && !$configReadOnly
+ || !$configFileWritable && \OCP\Util::needUpgrade()) {
+ $urlGenerator = Server::get(IURLGenerator::class);
+ $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
+
+ if (self::$CLI) {
+ echo $l->t('Cannot write into "config" directory!') . "\n";
+ echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
+ echo "\n";
+ echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
+ echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
+ exit;
+ } else {
+ Server::get(ITemplateManager::class)->printErrorPage(
+ $l->t('Cannot write into "config" directory!'),
+ $l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
+ . $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
+ . $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
+ 503
+ );
+ }
+ }
+ }
+
+ public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
+ if (defined('OC_CONSOLE')) {
+ return;
+ }
+ // Redirect to installer if not installed
+ if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
+ if (OC::$CLI) {
+ throw new Exception('Not installed');
+ } else {
+ $url = OC::$WEBROOT . '/index.php';
+ header('Location: ' . $url);
+ }
+ exit();
+ }
+ }
+
+ public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
+ // Allow ajax update script to execute without being stopped
+ if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
+ // send http status 503
+ http_response_code(503);
+ header('X-Nextcloud-Maintenance-Mode: 1');
+ header('Retry-After: 120');
+
+ // render error page
+ $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
+ \OCP\Util::addScript('core', 'maintenance');
+ \OCP\Util::addScript('core', 'common');
+ \OCP\Util::addStyle('core', 'guest');
+ $template->printPage();
+ die();
+ }
+ }
+
+ /**
+ * Prints the upgrade page
+ */
+ private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
+ $cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
+ $disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
+ $tooBig = false;
+ if (!$disableWebUpdater) {
+ $apps = Server::get(\OCP\App\IAppManager::class);
+ if ($apps->isEnabledForAnyone('user_ldap')) {
+ $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
+
+ $result = $qb->select($qb->func()->count('*', 'user_count'))
+ ->from('ldap_user_mapping')
+ ->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ $tooBig = ($row['user_count'] > 50);
+ }
+ if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
+ $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
+
+ $result = $qb->select($qb->func()->count('*', 'user_count'))
+ ->from('user_saml_users')
+ ->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ $tooBig = ($row['user_count'] > 50);
+ }
+ if (!$tooBig) {
+ // count users
+ $totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
+ $tooBig = ($totalUsers > 50);
+ }
+ }
+ $ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
+ && $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
+
+ Util::addTranslations('core');
+ Util::addScript('core', 'common');
+ Util::addScript('core', 'main');
+ Util::addScript('core', 'update');
+
+ $initialState = Server::get(IInitialStateService::class);
+ $serverVersion = Server::get(\OCP\ServerVersion::class);
+ if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
+ // send http status 503
+ http_response_code(503);
+ header('Retry-After: 120');
+
+ $urlGenerator = Server::get(IURLGenerator::class);
+ $initialState->provideInitialState('core', 'updaterView', 'adminCli');
+ $initialState->provideInitialState('core', 'updateInfo', [
+ 'cliUpgradeLink' => $cliUpgradeLink ?: $urlGenerator->linkToDocs('admin-cli-upgrade'),
+ 'productName' => self::getProductName(),
+ 'version' => $serverVersion->getVersionString(),
+ 'tooBig' => $tooBig,
+ ]);
+
+ // render error page
+ Server::get(ITemplateManager::class)
+ ->getTemplate('', 'update', 'guest')
+ ->printPage();
+ die();
+ }
+
+ // check whether this is a core update or apps update
+ $installedVersion = $systemConfig->getValue('version', '0.0.0');
+ $currentVersion = implode('.', $serverVersion->getVersion());
+
+ // if not a core upgrade, then it's apps upgrade
+ $isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
+
+ $oldTheme = $systemConfig->getValue('theme');
+ $systemConfig->setValue('theme', '');
+
+ /** @var \OC\App\AppManager $appManager */
+ $appManager = Server::get(\OCP\App\IAppManager::class);
+
+ // get third party apps
+ $ocVersion = $serverVersion->getVersion();
+ $ocVersion = implode('.', $ocVersion);
+ $incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
+ $incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
+ $incompatibleShippedApps = [];
+ $incompatibleDisabledApps = [];
+ foreach ($incompatibleApps as $appInfo) {
+ if ($appManager->isShipped($appInfo['id'])) {
+ $incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
+ }
+ if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
+ $incompatibleDisabledApps[] = $appInfo;
+ }
+ }
+
+ if (!empty($incompatibleShippedApps)) {
+ $l = Server::get(\OCP\L10N\IFactory::class)->get('core');
+ $hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
+ throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
+ }
+
+ $appConfig = Server::get(IAppConfig::class);
+ $appsToUpgrade = array_map(function ($app) use (&$appConfig) {
+ return [
+ 'id' => $app['id'],
+ 'name' => $app['name'],
+ 'version' => $app['version'],
+ 'oldVersion' => $appConfig->getValueString($app['id'], 'installed_version'),
+ ];
+ }, $appManager->getAppsNeedingUpgrade($ocVersion));
+
+ $params = [
+ 'appsToUpgrade' => $appsToUpgrade,
+ 'incompatibleAppsList' => $incompatibleDisabledApps,
+ 'isAppsOnlyUpgrade' => $isAppsOnlyUpgrade,
+ 'oldTheme' => $oldTheme,
+ 'productName' => self::getProductName(),
+ 'version' => $serverVersion->getVersionString(),
+ ];
+
+ $initialState->provideInitialState('core', 'updaterView', 'admin');
+ $initialState->provideInitialState('core', 'updateInfo', $params);
+ Server::get(ITemplateManager::class)
+ ->getTemplate('', 'update', 'guest')
+ ->printPage();
+ }
+
+ private static function getProductName(): string {
+ $productName = 'Nextcloud';
+ try {
+ $defaults = new \OC_Defaults();
+ $productName = $defaults->getName();
+ } catch (Throwable $error) {
+ // ignore
+ }
+ return $productName;
+ }
+
+ public static function initSession(): void {
+ $request = Server::get(IRequest::class);
+
+ // TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
+ // TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
+ // TODO: for further information.
+ // $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
+ // if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
+ // setcookie('cookie_test', 'test', time() + 3600);
+ // // Do not initialize the session if a request is authenticated directly
+ // // unless there is a session cookie already sent along
+ // return;
+ // }
+
+ if ($request->getServerProtocol() === 'https') {
+ ini_set('session.cookie_secure', 'true');
+ }
+
+ // prevents javascript from accessing php session cookies
+ ini_set('session.cookie_httponly', 'true');
+
+ // set the cookie path to the Nextcloud directory
+ $cookie_path = OC::$WEBROOT ? : '/';
+ ini_set('session.cookie_path', $cookie_path);
+
+ // set the cookie domain to the Nextcloud domain
+ $cookie_domain = self::$config->getValue('cookie_domain', '');
+ if ($cookie_domain) {
+ ini_set('session.cookie_domain', $cookie_domain);
+ }
+
+ // Do not initialize sessions for 'status.php' requests
+ // Monitoring endpoints can quickly flood session handlers
+ // and 'status.php' doesn't require sessions anyway
+ // We still need to run the ini_set above so that same-site cookies use the correct configuration.
+ if (str_ends_with($request->getScriptName(), '/status.php')) {
+ return;
+ }
+
+ // Let the session name be changed in the initSession Hook
+ $sessionName = OC_Util::getInstanceId();
+
+ try {
+ $logger = null;
+ if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
+ $logger = logger('core');
+ }
+
+ // set the session name to the instance id - which is unique
+ $session = new \OC\Session\Internal(
+ $sessionName,
+ $logger,
+ );
+
+ $cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
+ $session = $cryptoWrapper->wrapSession($session);
+ self::$server->setSession($session);
+
+ // if session can't be started break with http 500 error
+ } catch (Exception $e) {
+ Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
+ //show the user a detailed error page
+ Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
+ die();
+ }
+
+ //try to set the session lifetime
+ $sessionLifeTime = self::getSessionLifeTime();
+
+ // session timeout
+ if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
+ if (isset($_COOKIE[session_name()])) {
+ setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
+ }
+ Server::get(IUserSession::class)->logout();
+ }
+
+ if (!self::hasSessionRelaxedExpiry()) {
+ $session->set('LAST_ACTIVITY', time());
+ }
+ $session->close();
+ }
+
+ private static function getSessionLifeTime(): int {
+ return Server::get(IConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
+ }
+
+ /**
+ * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
+ */
+ public static function hasSessionRelaxedExpiry(): bool {
+ return Server::get(IConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
+ }
+
+ /**
+ * Try to set some values to the required Nextcloud default
+ */
+ public static function setRequiredIniValues(): void {
+ // Don't display errors and log them
+ @ini_set('display_errors', '0');
+ @ini_set('log_errors', '1');
+
+ // Try to configure php to enable big file uploads.
+ // This doesn't work always depending on the webserver and php configuration.
+ // Let's try to overwrite some defaults if they are smaller than 1 hour
+
+ if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
+ @ini_set('max_execution_time', strval(3600));
+ }
+
+ if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
+ @ini_set('max_input_time', strval(3600));
+ }
+
+ // Try to set the maximum execution time to the largest time limit we have
+ if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
+ @set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
+ }
+
+ @ini_set('default_charset', 'UTF-8');
+ @ini_set('gd.jpeg_ignore_warning', '1');
+ }
+
+ /**
+ * Send the same site cookies
+ */
+ private static function sendSameSiteCookies(): void {
+ $cookieParams = session_get_cookie_params();
+ $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
+ $policies = [
+ 'lax',
+ 'strict',
+ ];
+
+ // Append __Host to the cookie if it meets the requirements
+ $cookiePrefix = '';
+ if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
+ $cookiePrefix = '__Host-';
+ }
+
+ foreach ($policies as $policy) {
+ header(
+ sprintf(
+ 'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
+ $cookiePrefix,
+ $policy,
+ $cookieParams['path'],
+ $policy
+ ),
+ false
+ );
+ }
+ }
+
+ /**
+ * Same Site cookie to further mitigate CSRF attacks. This cookie has to
+ * be set in every request if cookies are sent to add a second level of
+ * defense against CSRF.
+ *
+ * If the cookie is not sent this will set the cookie and reload the page.
+ * We use an additional cookie since we want to protect logout CSRF and
+ * also we can't directly interfere with PHP's session mechanism.
+ */
+ private static function performSameSiteCookieProtection(IConfig $config): void {
+ $request = Server::get(IRequest::class);
+
+ // Some user agents are notorious and don't really properly follow HTTP
+ // specifications. For those, have an automated opt-out. Since the protection
+ // for remote.php is applied in base.php as starting point we need to opt out
+ // here.
+ $incompatibleUserAgents = $config->getSystemValue('csrf.optout');
+
+ // Fallback, if csrf.optout is unset
+ if (!is_array($incompatibleUserAgents)) {
+ $incompatibleUserAgents = [
+ // OS X Finder
+ '/^WebDAVFS/',
+ // Windows webdav drive
+ '/^Microsoft-WebDAV-MiniRedir/',
+ ];
+ }
+
+ if ($request->isUserAgent($incompatibleUserAgents)) {
+ return;
+ }
+
+ if (count($_COOKIE) > 0) {
+ $requestUri = $request->getScriptName();
+ $processingScript = explode('/', $requestUri);
+ $processingScript = $processingScript[count($processingScript) - 1];
+
+ if ($processingScript === 'index.php' // index.php routes are handled in the middleware
+ || $processingScript === 'cron.php' // and cron.php does not need any authentication at all
+ || $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
+ ) {
+ return;
+ }
+
+ // All other endpoints require the lax and the strict cookie
+ if (!$request->passesStrictCookieCheck()) {
+ logger('core')->warning('Request does not pass strict cookie check');
+ self::sendSameSiteCookies();
+ // Debug mode gets access to the resources without strict cookie
+ // due to the fact that the SabreDAV browser also lives there.
+ if (!$config->getSystemValueBool('debug', false)) {
+ http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
+ header('Content-Type: application/json');
+ echo json_encode(['error' => 'Strict Cookie has not been found in request']);
+ exit();
+ }
+ }
+ } elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
+ self::sendSameSiteCookies();
+ }
+ }
+
+ /**
+ * This function adds some security related headers to all requests served via base.php
+ * The implementation of this function has to happen here to ensure that all third-party
+ * components (e.g. SabreDAV) also benefit from this headers.
+ */
+ private static function addSecurityHeaders(): void {
+ /**
+ * FIXME: Content Security Policy for legacy components. This
+ * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
+ * is used everywhere.
+ * @see \OCP\AppFramework\Http\Response::getHeaders
+ */
+ $policy = 'default-src \'self\'; '
+ . 'script-src \'self\' \'nonce-' . Server::get(ContentSecurityPolicyNonceManager::class)->getNonce() . '\'; '
+ . 'style-src \'self\' \'unsafe-inline\'; '
+ . 'frame-src *; '
+ . 'img-src * data: blob:; '
+ . 'font-src \'self\' data:; '
+ . 'media-src *; '
+ . 'connect-src *; '
+ . 'object-src \'none\'; '
+ . 'base-uri \'self\'; ';
+ header('Content-Security-Policy:' . $policy);
+
+ // Send fallback headers for installations that don't have the possibility to send
+ // custom headers on the webserver side
+ if (getenv('modHeadersAvailable') !== 'true') {
+ header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
+ header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
+ header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains
+ header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
+ header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
+ }
+ }
+
+ public static function boot(): void {
+ // prevent any XML processing from loading external entities
+ libxml_set_external_entity_loader(static function () {
+ return null;
+ });
+
+ // Set default timezone before the Server object is booted
+ if (!date_default_timezone_set('UTC')) {
+ throw new \RuntimeException('Could not set timezone to UTC');
+ }
+
+ // calculate the root directories
+ OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
+
+ // register autoloader
+ $loaderStart = microtime(true);
+
+ self::$CLI = (php_sapi_name() == 'cli');
+
+ // Add default composer PSR-4 autoloader, ensure apcu to be disabled
+ self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
+ self::$composerAutoloader->setApcuPrefix(null);
+
+ // setup 3rdparty autoloader
+ $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
+ if (!file_exists($vendorAutoLoad)) {
+ 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".');
+ }
+ require_once $vendorAutoLoad;
+
+ $loaderEnd = microtime(true);
+
+ // load configs
+ if (defined('PHPUNIT_CONFIG_DIR')) {
+ self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
+ } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) {
+ self::$configDir = OC::$SERVERROOT . '/tests/config/';
+ } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
+ self::$configDir = rtrim($dir, '/') . '/';
+ } else {
+ self::$configDir = OC::$SERVERROOT . '/config/';
+ }
+ self::$config = new \OC\Config(self::$configDir);
+
+ // Enable lazy loading if activated
+ \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
+
+ try {
+ self::initPaths();
+ } catch (\RuntimeException $e) {
+ if (!self::$CLI) {
+ http_response_code(503);
+ }
+ // we can't use the template error page here, because this needs the
+ // DI container which isn't available yet
+ print($e->getMessage());
+ exit();
+ }
+
+ //$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
+ //$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
+ //$eventLogger->start('init', 'Initialize');
+ }
+
+ public static function init(): void {
+ // First handle PHP configuration and copy auth headers to the expected
+ // $_SERVER variable before doing anything Server object related
+ self::setRequiredIniValues();
+ self::handleAuthHeaders();
+
+ // set up the basic server
+ self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
+ self::$server->boot();
+
+ $loaderStart = microtime(true);
+
+ try {
+ $profiler = new BuiltInProfiler(
+ Server::get(IConfig::class),
+ Server::get(IRequest::class),
+ );
+ $profiler->start();
+ } catch (\Throwable $e) {
+ logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
+ }
+
+ if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
+ \OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
+ }
+
+ $eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
+ $eventLogger->start('init', 'Initialize');
+
+ // Override php.ini and log everything if we're troubleshooting
+ if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
+ error_reporting(E_ALL);
+ }
+
+ // initialize intl fallback if necessary
+ OC_Util::isSetLocaleWorking();
+
+ $config = Server::get(IConfig::class);
+ if (!defined('PHPUNIT_RUN')) {
+ $errorHandler = new OC\Log\ErrorHandler(
+ Server::get(\Psr\Log\LoggerInterface::class),
+ );
+ $exceptionHandler = [$errorHandler, 'onException'];
+ if ($config->getSystemValueBool('debug', false)) {
+ set_error_handler([$errorHandler, 'onAll'], E_ALL);
+ if (\OC::$CLI) {
+ $exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
+ }
+ } else {
+ set_error_handler([$errorHandler, 'onError']);
+ }
+ register_shutdown_function([$errorHandler, 'onShutdown']);
+ set_exception_handler($exceptionHandler);
+ }
+
+ /** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
+ $bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
+ $bootstrapCoordinator->runInitialRegistration();
+
+ $eventLogger->start('init_session', 'Initialize session');
+
+ // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
+ // see https://github.com/nextcloud/server/pull/2619
+ if (!function_exists('simplexml_load_file')) {
+ throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
+ }
+
+ $systemConfig = Server::get(\OC\SystemConfig::class);
+ $appManager = Server::get(\OCP\App\IAppManager::class);
+ if ($systemConfig->getValue('installed', false)) {
+ $appManager->loadApps(['session']);
+ }
+ if (!self::$CLI) {
+ self::initSession();
+ }
+ $eventLogger->end('init_session');
+ self::checkConfig();
+ self::checkInstalled($systemConfig);
+
+ if (!self::$CLI) {
+ self::addSecurityHeaders();
+ self::performSameSiteCookieProtection($config);
+ }
+
+ if (!defined('OC_CONSOLE')) {
+ $eventLogger->start('check_server', 'Run a few configuration checks');
+ $errors = OC_Util::checkServer($systemConfig);
+ if (count($errors) > 0) {
+ if (!self::$CLI) {
+ http_response_code(503);
+ Util::addStyle('guest');
+ try {
+ Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
+ exit;
+ } catch (\Exception $e) {
+ // In case any error happens when showing the error page, we simply fall back to posting the text.
+ // This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
+ }
+ }
+
+ // Convert l10n string into regular string for usage in database
+ $staticErrors = [];
+ foreach ($errors as $error) {
+ echo $error['error'] . "\n";
+ echo $error['hint'] . "\n\n";
+ $staticErrors[] = [
+ 'error' => (string)$error['error'],
+ 'hint' => (string)$error['hint'],
+ ];
+ }
+
+ try {
+ $config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
+ } catch (\Exception $e) {
+ echo('Writing to database failed');
+ }
+ exit(1);
+ } elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
+ $config->deleteAppValue('core', 'cronErrors');
+ }
+ $eventLogger->end('check_server');
+ }
+
+ // User and Groups
+ if (!$systemConfig->getValue('installed', false)) {
+ Server::get(ISession::class)->set('user_id', '');
+ }
+
+ $eventLogger->start('setup_backends', 'Setup group and user backends');
+ Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
+ Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
+
+ // Subscribe to the hook
+ \OCP\Util::connectHook(
+ '\OCA\Files_Sharing\API\Server2Server',
+ 'preLoginNameUsedAsUserName',
+ '\OC\User\Database',
+ 'preLoginNameUsedAsUserName'
+ );
+
+ //setup extra user backends
+ if (!\OCP\Util::needUpgrade()) {
+ OC_User::setupBackends();
+ } else {
+ // Run upgrades in incognito mode
+ OC_User::setIncognitoMode(true);
+ }
+ $eventLogger->end('setup_backends');
+
+ self::registerCleanupHooks($systemConfig);
+ self::registerShareHooks($systemConfig);
+ self::registerEncryptionWrapperAndHooks();
+ self::registerAccountHooks();
+ self::registerResourceCollectionHooks();
+ self::registerFileReferenceEventListener();
+ self::registerRenderReferenceEventListener();
+ self::registerAppRestrictionsHooks();
+
+ // Make sure that the application class is not loaded before the database is setup
+ if ($systemConfig->getValue('installed', false)) {
+ $appManager->loadApp('settings');
+ }
+
+ //make sure temporary files are cleaned up
+ $tmpManager = Server::get(\OCP\ITempManager::class);
+ register_shutdown_function([$tmpManager, 'clean']);
+ $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
+ register_shutdown_function([$lockProvider, 'releaseAll']);
+
+ // Check whether the sample configuration has been copied
+ if ($systemConfig->getValue('copied_sample_config', false)) {
+ $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
+ Server::get(ITemplateManager::class)->printErrorPage(
+ $l->t('Sample configuration detected'),
+ $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'),
+ 503
+ );
+ return;
+ }
+
+ $request = Server::get(IRequest::class);
+ $host = $request->getInsecureServerHost();
+ /**
+ * if the host passed in headers isn't trusted
+ * FIXME: Should not be in here at all :see_no_evil:
+ */
+ if (!OC::$CLI
+ && !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
+ && $config->getSystemValueBool('installed', false)
+ ) {
+ // Allow access to CSS resources
+ $isScssRequest = false;
+ if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
+ $isScssRequest = true;
+ }
+
+ if (substr($request->getRequestUri(), -11) === '/status.php') {
+ http_response_code(400);
+ header('Content-Type: application/json');
+ echo '{"error": "Trusted domain error.", "code": 15}';
+ exit();
+ }
+
+ if (!$isScssRequest) {
+ http_response_code(400);
+ Server::get(LoggerInterface::class)->info(
+ 'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
+ [
+ 'app' => 'core',
+ 'remoteAddress' => $request->getRemoteAddress(),
+ 'host' => $host,
+ ]
+ );
+
+ $tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
+ $tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
+ $tmpl->printPage();
+
+ exit();
+ }
+ }
+ $eventLogger->end('boot');
+ $eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
+ $eventLogger->start('runtime', 'Runtime');
+ $eventLogger->start('request', 'Full request after boot');
+ register_shutdown_function(function () use ($eventLogger) {
+ $eventLogger->end('request');
+ });
+
+ register_shutdown_function(function () use ($config) {
+ $memoryPeak = memory_get_peak_usage();
+ $debugModeEnabled = $config->getSystemValueBool('debug', false);
+ $memoryLimit = null;
+
+ if (!$debugModeEnabled) {
+ // Use the memory helper to get the real memory limit in bytes if debug mode is disabled
+ try {
+ $memoryInfo = new \OC\MemoryInfo();
+ $memoryLimit = $memoryInfo->getMemoryLimit();
+ } catch (Throwable $e) {
+ // Ignore any errors and fall back to hardcoded thresholds
+ }
+ }
+
+ // Check if a memory limit is configured and can be retrieved and determine log level if debug mode is disabled
+ if (!$debugModeEnabled && $memoryLimit !== null && $memoryLimit !== -1) {
+ $logLevel = match (true) {
+ $memoryPeak > $memoryLimit * 0.9 => ILogger::FATAL,
+ $memoryPeak > $memoryLimit * 0.75 => ILogger::ERROR,
+ $memoryPeak > $memoryLimit * 0.5 => ILogger::WARN,
+ default => null,
+ };
+
+ $memoryLimitIni = @ini_get('memory_limit');
+ $message = 'Request used ' . Util::humanFileSize($memoryPeak) . ' of memory. Memory limit: ' . ($memoryLimitIni ?: 'unknown');
+ } else {
+ // Fall back to hardcoded thresholds if memory_limit cannot be determined or if debug mode is enabled
+ $logLevel = match (true) {
+ $memoryPeak > 500_000_000 => ILogger::FATAL,
+ $memoryPeak > 400_000_000 => ILogger::ERROR,
+ $memoryPeak > 300_000_000 => ILogger::WARN,
+ default => null,
+ };
+
+ $message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
+ }
+
+ // Log the message
+ if ($logLevel !== null) {
+ $logger = Server::get(LoggerInterface::class);
+ $logger->log($logLevel, $message, ['app' => 'core']);
+ }
+ });
+ }
+
+ /**
+ * register hooks for the cleanup of cache and bruteforce protection
+ */
+ public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
+ //don't try to do this before we are properly setup
+ if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
+ // NOTE: This will be replaced to use OCP
+ $userSession = Server::get(\OC\User\Session::class);
+ $userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
+ if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
+ // reset brute force delay for this IP address and username
+ $uid = $userSession->getUser()->getUID();
+ $request = Server::get(IRequest::class);
+ $throttler = Server::get(IThrottler::class);
+ $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
+ }
+
+ try {
+ $cache = new \OC\Cache\File();
+ $cache->gc();
+ } catch (\OC\ServerNotAvailableException $e) {
+ // not a GC exception, pass it on
+ throw $e;
+ } catch (\OC\ForbiddenException $e) {
+ // filesystem blocked for this request, ignore
+ } catch (\Exception $e) {
+ // a GC exception should not prevent users from using OC,
+ // so log the exception
+ Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
+ 'app' => 'core',
+ 'exception' => $e,
+ ]);
+ }
+ });
+ }
+ }
+
+ private static function registerEncryptionWrapperAndHooks(): void {
+ /** @var \OC\Encryption\Manager */
+ $manager = Server::get(\OCP\Encryption\IManager::class);
+ Server::get(IEventDispatcher::class)->addListener(
+ BeforeFileSystemSetupEvent::class,
+ $manager->setupStorage(...),
+ );
+
+ $enabled = $manager->isEnabled();
+ if ($enabled) {
+ \OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
+ }
+ }
+
+ private static function registerAccountHooks(): void {
+ /** @var IEventDispatcher $dispatcher */
+ $dispatcher = Server::get(IEventDispatcher::class);
+ $dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
+ }
+
+ private static function registerAppRestrictionsHooks(): void {
+ /** @var \OC\Group\Manager $groupManager */
+ $groupManager = Server::get(\OCP\IGroupManager::class);
+ $groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
+ $appManager = Server::get(\OCP\App\IAppManager::class);
+ $apps = $appManager->getEnabledAppsForGroup($group);
+ foreach ($apps as $appId) {
+ $restrictions = $appManager->getAppRestriction($appId);
+ if (empty($restrictions)) {
+ continue;
+ }
+ $key = array_search($group->getGID(), $restrictions, true);
+ unset($restrictions[$key]);
+ $restrictions = array_values($restrictions);
+ if (empty($restrictions)) {
+ $appManager->disableApp($appId);
+ } else {
+ $appManager->enableAppForGroups($appId, $restrictions);
+ }
+ }
+ });
+ }
+
+ private static function registerResourceCollectionHooks(): void {
+ \OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
+ }
+
+ private static function registerFileReferenceEventListener(): void {
+ \OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
+ }
+
+ private static function registerRenderReferenceEventListener() {
+ \OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
+ }
+
+ /**
+ * register hooks for sharing
+ */
+ public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
+ if ($systemConfig->getValue('installed')) {
+
+ $dispatcher = Server::get(IEventDispatcher::class);
+ $dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
+ $dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
+ $dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
+ }
+ }
+
+ /**
+ * Handle the request
+ */
+ public static function handleRequest(): void {
+ Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
+ $systemConfig = Server::get(\OC\SystemConfig::class);
+
+ // Check if Nextcloud is installed or in maintenance (update) mode
+ if (!$systemConfig->getValue('installed', false)) {
+ Server::get(ISession::class)->clear();
+ $controller = Server::get(\OC\Core\Controller\SetupController::class);
+ $controller->run($_POST);
+ exit();
+ }
+
+ $request = Server::get(IRequest::class);
+ $request->throwDecodingExceptionIfAny();
+ $requestPath = $request->getRawPathInfo();
+ if ($requestPath === '/heartbeat') {
+ return;
+ }
+ if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
+ self::checkMaintenanceMode($systemConfig);
+
+ if (\OCP\Util::needUpgrade()) {
+ if (function_exists('opcache_reset')) {
+ opcache_reset();
+ }
+ if (!((bool)$systemConfig->getValue('maintenance', false))) {
+ self::printUpgradePage($systemConfig);
+ exit();
+ }
+ }
+ }
+
+ $appManager = Server::get(\OCP\App\IAppManager::class);
+
+ // Always load authentication apps
+ $appManager->loadApps(['authentication']);
+ $appManager->loadApps(['extended_authentication']);
+
+ // Load minimum set of apps
+ if (!\OCP\Util::needUpgrade()
+ && !((bool)$systemConfig->getValue('maintenance', false))) {
+ // For logged-in users: Load everything
+ if (Server::get(IUserSession::class)->isLoggedIn()) {
+ $appManager->loadApps();
+ } else {
+ // For guests: Load only filesystem and logging
+ $appManager->loadApps(['filesystem', 'logging']);
+
+ // Don't try to login when a client is trying to get a OAuth token.
+ // OAuth needs to support basic auth too, so the login is not valid
+ // inside Nextcloud and the Login exception would ruin it.
+ if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
+ try {
+ self::handleLogin($request);
+ } catch (DisabledUserException $e) {
+ // Disabled users would not be seen as logged in and
+ // trying to log them in would fail, so the login
+ // exception is ignored for the themed stylesheets and
+ // images.
+ if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
+ && $request->getRawPathInfo() !== '/apps/theming/image/background'
+ && $request->getRawPathInfo() !== '/apps/theming/image/logo'
+ && $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
+ && !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
+ && !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
+ throw $e;
+ }
+ }
+ }
+ }
+ }
+
+ if (!self::$CLI) {
+ try {
+ if (!\OCP\Util::needUpgrade()) {
+ $appManager->loadApps(['filesystem', 'logging']);
+ $appManager->loadApps();
+ }
+ Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
+ return;
+ } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
+ //header('HTTP/1.0 404 Not Found');
+ } catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
+ http_response_code(405);
+ return;
+ }
+ }
+
+ // Handle WebDAV
+ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
+ // not allowed any more to prevent people
+ // mounting this root directly.
+ // Users need to mount remote.php/webdav instead.
+ http_response_code(405);
+ return;
+ }
+
+ // Handle requests for JSON or XML
+ $acceptHeader = $request->getHeader('Accept');
+ if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
+ http_response_code(404);
+ return;
+ }
+
+ // Handle resources that can't be found
+ // This prevents browsers from redirecting to the default page and then
+ // attempting to parse HTML as CSS and similar.
+ $destinationHeader = $request->getHeader('Sec-Fetch-Dest');
+ if (in_array($destinationHeader, ['font', 'script', 'style'])) {
+ http_response_code(404);
+ return;
+ }
+
+ // Redirect to the default app or login only as an entry point
+ if ($requestPath === '') {
+ // Someone is logged in
+ $userSession = Server::get(IUserSession::class);
+ if ($userSession->isLoggedIn()) {
+ header('X-User-Id: ' . $userSession->getUser()?->getUID());
+ header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
+ } else {
+ // Not handled and not logged in
+ header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
+ }
+ return;
+ }
+
+ try {
+ Server::get(\OC\Route\Router::class)->match('/error/404');
+ } catch (\Exception $e) {
+ if (!$e instanceof MethodNotAllowedException) {
+ logger('core')->emergency($e->getMessage(), ['exception' => $e]);
+ }
+ $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
+ Server::get(ITemplateManager::class)->printErrorPage(
+ '404',
+ $l->t('The page could not be found on the server.'),
+ 404
+ );
+ }
+ }
+
+ /**
+ * Check login: apache auth, auth token, basic auth
+ */
+ public static function handleLogin(OCP\IRequest $request): bool {
+ if ($request->getHeader('X-Nextcloud-Federation')) {
+ return false;
+ }
+ $userSession = Server::get(\OC\User\Session::class);
+ if (OC_User::handleApacheAuth()) {
+ return true;
+ }
+ if (self::tryAppAPILogin($request)) {
+ return true;
+ }
+ if ($userSession->tryTokenLogin($request)) {
+ return true;
+ }
+ if (isset($_COOKIE['nc_username'])
+ && isset($_COOKIE['nc_token'])
+ && isset($_COOKIE['nc_session_id'])
+ && $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
+ return true;
+ }
+ if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
+ return true;
+ }
+ return false;
+ }
+
+ protected static function handleAuthHeaders(): void {
+ //copy http auth headers for apache+php-fcgid work around
+ if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
+ $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
+ }
+
+ // Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
+ $vars = [
+ 'HTTP_AUTHORIZATION', // apache+php-cgi work around
+ 'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
+ ];
+ foreach ($vars as $var) {
+ if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
+ $credentials = explode(':', base64_decode($matches[1]), 2);
+ if (count($credentials) === 2) {
+ $_SERVER['PHP_AUTH_USER'] = $credentials[0];
+ $_SERVER['PHP_AUTH_PW'] = $credentials[1];
+ break;
+ }
+ }
+ }
+ }
+
+ protected static function tryAppAPILogin(OCP\IRequest $request): bool {
+ if (!$request->getHeader('AUTHORIZATION-APP-API')) {
+ return false;
+ }
+ $appManager = Server::get(OCP\App\IAppManager::class);
+ if (!$appManager->isEnabledForAnyone('app_api')) {
+ return false;
+ }
+ try {
+ $appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
+ return $appAPIService->validateExAppRequestToNC($request);
+ } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
+ return false;
+ }
+ }
+}
diff --git a/lib/base.php b/lib/base.php
index a11334c50cfd7..8012db9d76314 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -1,1291 +1,14 @@
[
- 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
- 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
- ],
- ];
- if (isset($_SERVER['REMOTE_ADDR'])) {
- $params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
- }
- $fakeRequest = new \OC\AppFramework\Http\Request(
- $params,
- new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
- new \OC\AllConfig(new \OC\SystemConfig(self::$config))
- );
- $scriptName = $fakeRequest->getScriptName();
- if (substr($scriptName, -1) == '/') {
- $scriptName .= 'index.php';
- //make sure suburi follows the same rules as scriptName
- if (substr(OC::$SUBURI, -9) != 'index.php') {
- if (substr(OC::$SUBURI, -1) != '/') {
- OC::$SUBURI = OC::$SUBURI . '/';
- }
- OC::$SUBURI = OC::$SUBURI . 'index.php';
- }
- }
-
- if (OC::$CLI) {
- OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
- } else {
- if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
- OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
-
- if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
- OC::$WEBROOT = '/' . OC::$WEBROOT;
- }
- } else {
- // The scriptName is not ending with OC::$SUBURI
- // This most likely means that we are calling from CLI.
- // However some cron jobs still need to generate
- // a web URL, so we use overwritewebroot as a fallback.
- OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
- }
-
- // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
- // slash which is required by URL generation.
- if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
- && substr($_SERVER['REQUEST_URI'], -1) !== '/') {
- header('Location: ' . \OC::$WEBROOT . '/');
- exit();
- }
- }
-
- // search the apps folder
- $config_paths = self::$config->getValue('apps_paths', []);
- if (!empty($config_paths)) {
- foreach ($config_paths as $paths) {
- if (isset($paths['url']) && isset($paths['path'])) {
- $paths['url'] = rtrim($paths['url'], '/');
- $paths['path'] = rtrim($paths['path'], '/');
- OC::$APPSROOTS[] = $paths;
- }
- }
- } elseif (file_exists(OC::$SERVERROOT . '/apps')) {
- OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
- }
-
- if (empty(OC::$APPSROOTS)) {
- throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
- . '. You can also configure the location in the config.php file.');
- }
- $paths = [];
- foreach (OC::$APPSROOTS as $path) {
- $paths[] = $path['path'];
- if (!is_dir($path['path'])) {
- throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
- . ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
- }
- }
-
- // set the right include path
- set_include_path(
- implode(PATH_SEPARATOR, $paths)
- );
- }
-
- public static function checkConfig(): void {
- // Create config if it does not already exist
- $configFilePath = self::$configDir . '/config.php';
- if (!file_exists($configFilePath)) {
- @touch($configFilePath);
- }
-
- // Check if config is writable
- $configFileWritable = is_writable($configFilePath);
- $configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
- if (!$configFileWritable && !$configReadOnly
- || !$configFileWritable && \OCP\Util::needUpgrade()) {
- $urlGenerator = Server::get(IURLGenerator::class);
- $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
-
- if (self::$CLI) {
- echo $l->t('Cannot write into "config" directory!') . "\n";
- echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
- echo "\n";
- echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
- echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
- exit;
- } else {
- Server::get(ITemplateManager::class)->printErrorPage(
- $l->t('Cannot write into "config" directory!'),
- $l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
- . $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
- . $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
- 503
- );
- }
- }
- }
-
- public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
- if (defined('OC_CONSOLE')) {
- return;
- }
- // Redirect to installer if not installed
- if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
- if (OC::$CLI) {
- throw new Exception('Not installed');
- } else {
- $url = OC::$WEBROOT . '/index.php';
- header('Location: ' . $url);
- }
- exit();
- }
- }
-
- public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
- // Allow ajax update script to execute without being stopped
- if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
- // send http status 503
- http_response_code(503);
- header('X-Nextcloud-Maintenance-Mode: 1');
- header('Retry-After: 120');
-
- // render error page
- $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
- \OCP\Util::addScript('core', 'maintenance');
- \OCP\Util::addScript('core', 'common');
- \OCP\Util::addStyle('core', 'guest');
- $template->printPage();
- die();
- }
- }
-
- /**
- * Prints the upgrade page
- */
- private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
- $cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
- $disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
- $tooBig = false;
- if (!$disableWebUpdater) {
- $apps = Server::get(\OCP\App\IAppManager::class);
- if ($apps->isEnabledForAnyone('user_ldap')) {
- $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
-
- $result = $qb->select($qb->func()->count('*', 'user_count'))
- ->from('ldap_user_mapping')
- ->executeQuery();
- $row = $result->fetch();
- $result->closeCursor();
-
- $tooBig = ($row['user_count'] > 50);
- }
- if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
- $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
-
- $result = $qb->select($qb->func()->count('*', 'user_count'))
- ->from('user_saml_users')
- ->executeQuery();
- $row = $result->fetch();
- $result->closeCursor();
-
- $tooBig = ($row['user_count'] > 50);
- }
- if (!$tooBig) {
- // count users
- $totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
- $tooBig = ($totalUsers > 50);
- }
- }
- $ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
- && $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
-
- Util::addTranslations('core');
- Util::addScript('core', 'common');
- Util::addScript('core', 'main');
- Util::addScript('core', 'update');
-
- $initialState = Server::get(IInitialStateService::class);
- $serverVersion = Server::get(\OCP\ServerVersion::class);
- if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
- // send http status 503
- http_response_code(503);
- header('Retry-After: 120');
-
- $urlGenerator = Server::get(IURLGenerator::class);
- $initialState->provideInitialState('core', 'updaterView', 'adminCli');
- $initialState->provideInitialState('core', 'updateInfo', [
- 'cliUpgradeLink' => $cliUpgradeLink ?: $urlGenerator->linkToDocs('admin-cli-upgrade'),
- 'productName' => self::getProductName(),
- 'version' => $serverVersion->getVersionString(),
- 'tooBig' => $tooBig,
- ]);
-
- // render error page
- Server::get(ITemplateManager::class)
- ->getTemplate('', 'update', 'guest')
- ->printPage();
- die();
- }
-
- // check whether this is a core update or apps update
- $installedVersion = $systemConfig->getValue('version', '0.0.0');
- $currentVersion = implode('.', $serverVersion->getVersion());
-
- // if not a core upgrade, then it's apps upgrade
- $isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
-
- $oldTheme = $systemConfig->getValue('theme');
- $systemConfig->setValue('theme', '');
-
- /** @var \OC\App\AppManager $appManager */
- $appManager = Server::get(\OCP\App\IAppManager::class);
-
- // get third party apps
- $ocVersion = $serverVersion->getVersion();
- $ocVersion = implode('.', $ocVersion);
- $incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
- $incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
- $incompatibleShippedApps = [];
- $incompatibleDisabledApps = [];
- foreach ($incompatibleApps as $appInfo) {
- if ($appManager->isShipped($appInfo['id'])) {
- $incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
- }
- if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
- $incompatibleDisabledApps[] = $appInfo;
- }
- }
-
- if (!empty($incompatibleShippedApps)) {
- $l = Server::get(\OCP\L10N\IFactory::class)->get('core');
- $hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
- throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
- }
-
- $appConfig = Server::get(IAppConfig::class);
- $appsToUpgrade = array_map(function ($app) use (&$appConfig) {
- return [
- 'id' => $app['id'],
- 'name' => $app['name'],
- 'version' => $app['version'],
- 'oldVersion' => $appConfig->getValueString($app['id'], 'installed_version'),
- ];
- }, $appManager->getAppsNeedingUpgrade($ocVersion));
-
- $params = [
- 'appsToUpgrade' => $appsToUpgrade,
- 'incompatibleAppsList' => $incompatibleDisabledApps,
- 'isAppsOnlyUpgrade' => $isAppsOnlyUpgrade,
- 'oldTheme' => $oldTheme,
- 'productName' => self::getProductName(),
- 'version' => $serverVersion->getVersionString(),
- ];
-
- $initialState->provideInitialState('core', 'updaterView', 'admin');
- $initialState->provideInitialState('core', 'updateInfo', $params);
- Server::get(ITemplateManager::class)
- ->getTemplate('', 'update', 'guest')
- ->printPage();
- }
-
- private static function getProductName(): string {
- $productName = 'Nextcloud';
- try {
- $defaults = new \OC_Defaults();
- $productName = $defaults->getName();
- } catch (Throwable $error) {
- // ignore
- }
- return $productName;
- }
-
- public static function initSession(): void {
- $request = Server::get(IRequest::class);
-
- // TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
- // TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
- // TODO: for further information.
- // $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
- // if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
- // setcookie('cookie_test', 'test', time() + 3600);
- // // Do not initialize the session if a request is authenticated directly
- // // unless there is a session cookie already sent along
- // return;
- // }
-
- if ($request->getServerProtocol() === 'https') {
- ini_set('session.cookie_secure', 'true');
- }
-
- // prevents javascript from accessing php session cookies
- ini_set('session.cookie_httponly', 'true');
-
- // set the cookie path to the Nextcloud directory
- $cookie_path = OC::$WEBROOT ? : '/';
- ini_set('session.cookie_path', $cookie_path);
-
- // set the cookie domain to the Nextcloud domain
- $cookie_domain = self::$config->getValue('cookie_domain', '');
- if ($cookie_domain) {
- ini_set('session.cookie_domain', $cookie_domain);
- }
-
- // Do not initialize sessions for 'status.php' requests
- // Monitoring endpoints can quickly flood session handlers
- // and 'status.php' doesn't require sessions anyway
- // We still need to run the ini_set above so that same-site cookies use the correct configuration.
- if (str_ends_with($request->getScriptName(), '/status.php')) {
- return;
- }
-
- // Let the session name be changed in the initSession Hook
- $sessionName = OC_Util::getInstanceId();
-
- try {
- $logger = null;
- if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
- $logger = logger('core');
- }
-
- // set the session name to the instance id - which is unique
- $session = new \OC\Session\Internal(
- $sessionName,
- $logger,
- );
-
- $cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
- $session = $cryptoWrapper->wrapSession($session);
- self::$server->setSession($session);
-
- // if session can't be started break with http 500 error
- } catch (Exception $e) {
- Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
- //show the user a detailed error page
- Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
- die();
- }
-
- //try to set the session lifetime
- $sessionLifeTime = self::getSessionLifeTime();
-
- // session timeout
- if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
- if (isset($_COOKIE[session_name()])) {
- setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
- }
- Server::get(IUserSession::class)->logout();
- }
-
- if (!self::hasSessionRelaxedExpiry()) {
- $session->set('LAST_ACTIVITY', time());
- }
- $session->close();
- }
-
- private static function getSessionLifeTime(): int {
- return Server::get(IConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
- }
-
- /**
- * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
- */
- public static function hasSessionRelaxedExpiry(): bool {
- return Server::get(IConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
- }
-
- /**
- * Try to set some values to the required Nextcloud default
- */
- public static function setRequiredIniValues(): void {
- // Don't display errors and log them
- @ini_set('display_errors', '0');
- @ini_set('log_errors', '1');
-
- // Try to configure php to enable big file uploads.
- // This doesn't work always depending on the webserver and php configuration.
- // Let's try to overwrite some defaults if they are smaller than 1 hour
-
- if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
- @ini_set('max_execution_time', strval(3600));
- }
-
- if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
- @ini_set('max_input_time', strval(3600));
- }
-
- // Try to set the maximum execution time to the largest time limit we have
- if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
- @set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
- }
-
- @ini_set('default_charset', 'UTF-8');
- @ini_set('gd.jpeg_ignore_warning', '1');
- }
-
- /**
- * Send the same site cookies
- */
- private static function sendSameSiteCookies(): void {
- $cookieParams = session_get_cookie_params();
- $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
- $policies = [
- 'lax',
- 'strict',
- ];
-
- // Append __Host to the cookie if it meets the requirements
- $cookiePrefix = '';
- if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
- $cookiePrefix = '__Host-';
- }
-
- foreach ($policies as $policy) {
- header(
- sprintf(
- 'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
- $cookiePrefix,
- $policy,
- $cookieParams['path'],
- $policy
- ),
- false
- );
- }
- }
-
- /**
- * Same Site cookie to further mitigate CSRF attacks. This cookie has to
- * be set in every request if cookies are sent to add a second level of
- * defense against CSRF.
- *
- * If the cookie is not sent this will set the cookie and reload the page.
- * We use an additional cookie since we want to protect logout CSRF and
- * also we can't directly interfere with PHP's session mechanism.
- */
- private static function performSameSiteCookieProtection(IConfig $config): void {
- $request = Server::get(IRequest::class);
-
- // Some user agents are notorious and don't really properly follow HTTP
- // specifications. For those, have an automated opt-out. Since the protection
- // for remote.php is applied in base.php as starting point we need to opt out
- // here.
- $incompatibleUserAgents = $config->getSystemValue('csrf.optout');
-
- // Fallback, if csrf.optout is unset
- if (!is_array($incompatibleUserAgents)) {
- $incompatibleUserAgents = [
- // OS X Finder
- '/^WebDAVFS/',
- // Windows webdav drive
- '/^Microsoft-WebDAV-MiniRedir/',
- ];
- }
-
- if ($request->isUserAgent($incompatibleUserAgents)) {
- return;
- }
-
- if (count($_COOKIE) > 0) {
- $requestUri = $request->getScriptName();
- $processingScript = explode('/', $requestUri);
- $processingScript = $processingScript[count($processingScript) - 1];
-
- if ($processingScript === 'index.php' // index.php routes are handled in the middleware
- || $processingScript === 'cron.php' // and cron.php does not need any authentication at all
- || $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
- ) {
- return;
- }
-
- // All other endpoints require the lax and the strict cookie
- if (!$request->passesStrictCookieCheck()) {
- logger('core')->warning('Request does not pass strict cookie check');
- self::sendSameSiteCookies();
- // Debug mode gets access to the resources without strict cookie
- // due to the fact that the SabreDAV browser also lives there.
- if (!$config->getSystemValueBool('debug', false)) {
- http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
- header('Content-Type: application/json');
- echo json_encode(['error' => 'Strict Cookie has not been found in request']);
- exit();
- }
- }
- } elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
- self::sendSameSiteCookies();
- }
- }
-
- /**
- * This function adds some security related headers to all requests served via base.php
- * The implementation of this function has to happen here to ensure that all third-party
- * components (e.g. SabreDAV) also benefit from this headers.
- */
- private static function addSecurityHeaders(): void {
- /**
- * FIXME: Content Security Policy for legacy components. This
- * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
- * is used everywhere.
- * @see \OCP\AppFramework\Http\Response::getHeaders
- */
- $policy = 'default-src \'self\'; '
- . 'script-src \'self\' \'nonce-' . Server::get(ContentSecurityPolicyNonceManager::class)->getNonce() . '\'; '
- . 'style-src \'self\' \'unsafe-inline\'; '
- . 'frame-src *; '
- . 'img-src * data: blob:; '
- . 'font-src \'self\' data:; '
- . 'media-src *; '
- . 'connect-src *; '
- . 'object-src \'none\'; '
- . 'base-uri \'self\'; ';
- header('Content-Security-Policy:' . $policy);
-
- // Send fallback headers for installations that don't have the possibility to send
- // custom headers on the webserver side
- if (getenv('modHeadersAvailable') !== 'true') {
- header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
- header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
- header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains
- header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
- header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
- }
- }
-
- public static function init(): void {
- // First handle PHP configuration and copy auth headers to the expected
- // $_SERVER variable before doing anything Server object related
- self::setRequiredIniValues();
- self::handleAuthHeaders();
-
- // prevent any XML processing from loading external entities
- libxml_set_external_entity_loader(static function () {
- return null;
- });
-
- // Set default timezone before the Server object is booted
- if (!date_default_timezone_set('UTC')) {
- throw new \RuntimeException('Could not set timezone to UTC');
- }
-
- // calculate the root directories
- OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
-
- // register autoloader
- $loaderStart = microtime(true);
-
- self::$CLI = (php_sapi_name() == 'cli');
-
- // Add default composer PSR-4 autoloader, ensure apcu to be disabled
- self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
- self::$composerAutoloader->setApcuPrefix(null);
-
-
- try {
- self::initPaths();
- // setup 3rdparty autoloader
- $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
- if (!file_exists($vendorAutoLoad)) {
- 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".');
- }
- require_once $vendorAutoLoad;
- } catch (\RuntimeException $e) {
- if (!self::$CLI) {
- http_response_code(503);
- }
- // we can't use the template error page here, because this needs the
- // DI container which isn't available yet
- print($e->getMessage());
- exit();
- }
- $loaderEnd = microtime(true);
-
- // Enable lazy loading if activated
- \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
-
- // setup the basic server
- self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
- self::$server->boot();
-
- try {
- $profiler = new BuiltInProfiler(
- Server::get(IConfig::class),
- Server::get(IRequest::class),
- );
- $profiler->start();
- } catch (\Throwable $e) {
- logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
- }
-
- if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
- \OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
- }
-
- $eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
- $eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
- $eventLogger->start('boot', 'Initialize');
-
- // Override php.ini and log everything if we're troubleshooting
- if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
- error_reporting(E_ALL);
- }
-
- // initialize intl fallback if necessary
- OC_Util::isSetLocaleWorking();
-
- $config = Server::get(IConfig::class);
- if (!defined('PHPUNIT_RUN')) {
- $errorHandler = new OC\Log\ErrorHandler(
- Server::get(\Psr\Log\LoggerInterface::class),
- );
- $exceptionHandler = [$errorHandler, 'onException'];
- if ($config->getSystemValueBool('debug', false)) {
- set_error_handler([$errorHandler, 'onAll'], E_ALL);
- if (\OC::$CLI) {
- $exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
- }
- } else {
- set_error_handler([$errorHandler, 'onError']);
- }
- register_shutdown_function([$errorHandler, 'onShutdown']);
- set_exception_handler($exceptionHandler);
- }
-
- /** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
- $bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
- $bootstrapCoordinator->runInitialRegistration();
-
- $eventLogger->start('init_session', 'Initialize session');
-
- // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
- // see https://github.com/nextcloud/server/pull/2619
- if (!function_exists('simplexml_load_file')) {
- throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
- }
-
- $systemConfig = Server::get(\OC\SystemConfig::class);
- $appManager = Server::get(\OCP\App\IAppManager::class);
- if ($systemConfig->getValue('installed', false)) {
- $appManager->loadApps(['session']);
- }
- if (!self::$CLI) {
- self::initSession();
- }
- $eventLogger->end('init_session');
- self::checkConfig();
- self::checkInstalled($systemConfig);
-
- if (!self::$CLI) {
- self::addSecurityHeaders();
- self::performSameSiteCookieProtection($config);
- }
-
- if (!defined('OC_CONSOLE')) {
- $eventLogger->start('check_server', 'Run a few configuration checks');
- $errors = OC_Util::checkServer($systemConfig);
- if (count($errors) > 0) {
- if (!self::$CLI) {
- http_response_code(503);
- Util::addStyle('guest');
- try {
- Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
- exit;
- } catch (\Exception $e) {
- // In case any error happens when showing the error page, we simply fall back to posting the text.
- // This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
- }
- }
-
- // Convert l10n string into regular string for usage in database
- $staticErrors = [];
- foreach ($errors as $error) {
- echo $error['error'] . "\n";
- echo $error['hint'] . "\n\n";
- $staticErrors[] = [
- 'error' => (string)$error['error'],
- 'hint' => (string)$error['hint'],
- ];
- }
-
- try {
- $config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
- } catch (\Exception $e) {
- echo('Writing to database failed');
- }
- exit(1);
- } elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
- $config->deleteAppValue('core', 'cronErrors');
- }
- $eventLogger->end('check_server');
- }
-
- // User and Groups
- if (!$systemConfig->getValue('installed', false)) {
- Server::get(ISession::class)->set('user_id', '');
- }
-
- $eventLogger->start('setup_backends', 'Setup group and user backends');
- Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
- Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
-
- // Subscribe to the hook
- \OCP\Util::connectHook(
- '\OCA\Files_Sharing\API\Server2Server',
- 'preLoginNameUsedAsUserName',
- '\OC\User\Database',
- 'preLoginNameUsedAsUserName'
- );
-
- //setup extra user backends
- if (!\OCP\Util::needUpgrade()) {
- OC_User::setupBackends();
- } else {
- // Run upgrades in incognito mode
- OC_User::setIncognitoMode(true);
- }
- $eventLogger->end('setup_backends');
-
- self::registerCleanupHooks($systemConfig);
- self::registerShareHooks($systemConfig);
- self::registerEncryptionWrapperAndHooks();
- self::registerAccountHooks();
- self::registerResourceCollectionHooks();
- self::registerFileReferenceEventListener();
- self::registerRenderReferenceEventListener();
- self::registerAppRestrictionsHooks();
-
- // Make sure that the application class is not loaded before the database is setup
- if ($systemConfig->getValue('installed', false)) {
- $appManager->loadApp('settings');
- }
-
- //make sure temporary files are cleaned up
- $tmpManager = Server::get(\OCP\ITempManager::class);
- register_shutdown_function([$tmpManager, 'clean']);
- $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
- register_shutdown_function([$lockProvider, 'releaseAll']);
-
- // Check whether the sample configuration has been copied
- if ($systemConfig->getValue('copied_sample_config', false)) {
- $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
- Server::get(ITemplateManager::class)->printErrorPage(
- $l->t('Sample configuration detected'),
- $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'),
- 503
- );
- return;
- }
-
- $request = Server::get(IRequest::class);
- $host = $request->getInsecureServerHost();
- /**
- * if the host passed in headers isn't trusted
- * FIXME: Should not be in here at all :see_no_evil:
- */
- if (!OC::$CLI
- && !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
- && $config->getSystemValueBool('installed', false)
- ) {
- // Allow access to CSS resources
- $isScssRequest = false;
- if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
- $isScssRequest = true;
- }
-
- if (substr($request->getRequestUri(), -11) === '/status.php') {
- http_response_code(400);
- header('Content-Type: application/json');
- echo '{"error": "Trusted domain error.", "code": 15}';
- exit();
- }
-
- if (!$isScssRequest) {
- http_response_code(400);
- Server::get(LoggerInterface::class)->info(
- 'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
- [
- 'app' => 'core',
- 'remoteAddress' => $request->getRemoteAddress(),
- 'host' => $host,
- ]
- );
-
- $tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
- $tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
- $tmpl->printPage();
-
- exit();
- }
- }
- $eventLogger->end('boot');
- $eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
- $eventLogger->start('runtime', 'Runtime');
- $eventLogger->start('request', 'Full request after boot');
- register_shutdown_function(function () use ($eventLogger) {
- $eventLogger->end('request');
- });
-
- register_shutdown_function(function () use ($config) {
- $memoryPeak = memory_get_peak_usage();
- $debugModeEnabled = $config->getSystemValueBool('debug', false);
- $memoryLimit = null;
-
- if (!$debugModeEnabled) {
- // Use the memory helper to get the real memory limit in bytes if debug mode is disabled
- try {
- $memoryInfo = new \OC\MemoryInfo();
- $memoryLimit = $memoryInfo->getMemoryLimit();
- } catch (Throwable $e) {
- // Ignore any errors and fall back to hardcoded thresholds
- }
- }
-
- // Check if a memory limit is configured and can be retrieved and determine log level if debug mode is disabled
- if (!$debugModeEnabled && $memoryLimit !== null && $memoryLimit !== -1) {
- $logLevel = match (true) {
- $memoryPeak > $memoryLimit * 0.9 => ILogger::FATAL,
- $memoryPeak > $memoryLimit * 0.75 => ILogger::ERROR,
- $memoryPeak > $memoryLimit * 0.5 => ILogger::WARN,
- default => null,
- };
-
- $memoryLimitIni = @ini_get('memory_limit');
- $message = 'Request used ' . Util::humanFileSize($memoryPeak) . ' of memory. Memory limit: ' . ($memoryLimitIni ?: 'unknown');
- } else {
- // Fall back to hardcoded thresholds if memory_limit cannot be determined or if debug mode is enabled
- $logLevel = match (true) {
- $memoryPeak > 500_000_000 => ILogger::FATAL,
- $memoryPeak > 400_000_000 => ILogger::ERROR,
- $memoryPeak > 300_000_000 => ILogger::WARN,
- default => null,
- };
-
- $message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
- }
-
- // Log the message
- if ($logLevel !== null) {
- $logger = Server::get(LoggerInterface::class);
- $logger->log($logLevel, $message, ['app' => 'core']);
- }
- });
- }
-
- /**
- * register hooks for the cleanup of cache and bruteforce protection
- */
- public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
- //don't try to do this before we are properly setup
- if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
- // NOTE: This will be replaced to use OCP
- $userSession = Server::get(\OC\User\Session::class);
- $userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
- if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
- // reset brute force delay for this IP address and username
- $uid = $userSession->getUser()->getUID();
- $request = Server::get(IRequest::class);
- $throttler = Server::get(IThrottler::class);
- $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
- }
-
- try {
- $cache = new \OC\Cache\File();
- $cache->gc();
- } catch (\OC\ServerNotAvailableException $e) {
- // not a GC exception, pass it on
- throw $e;
- } catch (\OC\ForbiddenException $e) {
- // filesystem blocked for this request, ignore
- } catch (\Exception $e) {
- // a GC exception should not prevent users from using OC,
- // so log the exception
- Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
- 'app' => 'core',
- 'exception' => $e,
- ]);
- }
- });
- }
- }
-
- private static function registerEncryptionWrapperAndHooks(): void {
- /** @var \OC\Encryption\Manager */
- $manager = Server::get(\OCP\Encryption\IManager::class);
- Server::get(IEventDispatcher::class)->addListener(
- BeforeFileSystemSetupEvent::class,
- $manager->setupStorage(...),
- );
-
- $enabled = $manager->isEnabled();
- if ($enabled) {
- \OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
- }
- }
-
- private static function registerAccountHooks(): void {
- /** @var IEventDispatcher $dispatcher */
- $dispatcher = Server::get(IEventDispatcher::class);
- $dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
- }
-
- private static function registerAppRestrictionsHooks(): void {
- /** @var \OC\Group\Manager $groupManager */
- $groupManager = Server::get(\OCP\IGroupManager::class);
- $groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
- $appManager = Server::get(\OCP\App\IAppManager::class);
- $apps = $appManager->getEnabledAppsForGroup($group);
- foreach ($apps as $appId) {
- $restrictions = $appManager->getAppRestriction($appId);
- if (empty($restrictions)) {
- continue;
- }
- $key = array_search($group->getGID(), $restrictions, true);
- unset($restrictions[$key]);
- $restrictions = array_values($restrictions);
- if (empty($restrictions)) {
- $appManager->disableApp($appId);
- } else {
- $appManager->enableAppForGroups($appId, $restrictions);
- }
- }
- });
- }
-
- private static function registerResourceCollectionHooks(): void {
- \OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
- }
-
- private static function registerFileReferenceEventListener(): void {
- \OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
- }
-
- private static function registerRenderReferenceEventListener() {
- \OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
- }
-
- /**
- * register hooks for sharing
- */
- public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
- if ($systemConfig->getValue('installed')) {
-
- $dispatcher = Server::get(IEventDispatcher::class);
- $dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
- $dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
- $dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
- }
- }
-
- /**
- * Handle the request
- */
- public static function handleRequest(): void {
- Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
- $systemConfig = Server::get(\OC\SystemConfig::class);
-
- // Check if Nextcloud is installed or in maintenance (update) mode
- if (!$systemConfig->getValue('installed', false)) {
- Server::get(ISession::class)->clear();
- $controller = Server::get(\OC\Core\Controller\SetupController::class);
- $controller->run($_POST);
- exit();
- }
-
- $request = Server::get(IRequest::class);
- $request->throwDecodingExceptionIfAny();
- $requestPath = $request->getRawPathInfo();
- if ($requestPath === '/heartbeat') {
- return;
- }
- if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
- self::checkMaintenanceMode($systemConfig);
-
- if (\OCP\Util::needUpgrade()) {
- if (function_exists('opcache_reset')) {
- opcache_reset();
- }
- if (!((bool)$systemConfig->getValue('maintenance', false))) {
- self::printUpgradePage($systemConfig);
- exit();
- }
- }
- }
-
- $appManager = Server::get(\OCP\App\IAppManager::class);
-
- // Always load authentication apps
- $appManager->loadApps(['authentication']);
- $appManager->loadApps(['extended_authentication']);
-
- // Load minimum set of apps
- if (!\OCP\Util::needUpgrade()
- && !((bool)$systemConfig->getValue('maintenance', false))) {
- // For logged-in users: Load everything
- if (Server::get(IUserSession::class)->isLoggedIn()) {
- $appManager->loadApps();
- } else {
- // For guests: Load only filesystem and logging
- $appManager->loadApps(['filesystem', 'logging']);
-
- // Don't try to login when a client is trying to get a OAuth token.
- // OAuth needs to support basic auth too, so the login is not valid
- // inside Nextcloud and the Login exception would ruin it.
- if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
- try {
- self::handleLogin($request);
- } catch (DisabledUserException $e) {
- // Disabled users would not be seen as logged in and
- // trying to log them in would fail, so the login
- // exception is ignored for the themed stylesheets and
- // images.
- if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
- && $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
- && $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
- && $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
- && $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
- && $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
- && $request->getRawPathInfo() !== '/apps/theming/image/background'
- && $request->getRawPathInfo() !== '/apps/theming/image/logo'
- && $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
- && !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
- && !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
- throw $e;
- }
- }
- }
- }
- }
-
- if (!self::$CLI) {
- try {
- if (!\OCP\Util::needUpgrade()) {
- $appManager->loadApps(['filesystem', 'logging']);
- $appManager->loadApps();
- }
- Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
- return;
- } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
- //header('HTTP/1.0 404 Not Found');
- } catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
- http_response_code(405);
- return;
- }
- }
-
- // Handle WebDAV
- if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
- // not allowed any more to prevent people
- // mounting this root directly.
- // Users need to mount remote.php/webdav instead.
- http_response_code(405);
- return;
- }
-
- // Handle requests for JSON or XML
- $acceptHeader = $request->getHeader('Accept');
- if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
- http_response_code(404);
- return;
- }
-
- // Handle resources that can't be found
- // This prevents browsers from redirecting to the default page and then
- // attempting to parse HTML as CSS and similar.
- $destinationHeader = $request->getHeader('Sec-Fetch-Dest');
- if (in_array($destinationHeader, ['font', 'script', 'style'])) {
- http_response_code(404);
- return;
- }
-
- // Redirect to the default app or login only as an entry point
- if ($requestPath === '') {
- // Someone is logged in
- $userSession = Server::get(IUserSession::class);
- if ($userSession->isLoggedIn()) {
- header('X-User-Id: ' . $userSession->getUser()?->getUID());
- header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
- } else {
- // Not handled and not logged in
- header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
- }
- return;
- }
-
- try {
- Server::get(\OC\Route\Router::class)->match('/error/404');
- } catch (\Exception $e) {
- if (!$e instanceof MethodNotAllowedException) {
- logger('core')->emergency($e->getMessage(), ['exception' => $e]);
- }
- $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
- Server::get(ITemplateManager::class)->printErrorPage(
- '404',
- $l->t('The page could not be found on the server.'),
- 404
- );
- }
- }
-
- /**
- * Check login: apache auth, auth token, basic auth
- */
- public static function handleLogin(OCP\IRequest $request): bool {
- if ($request->getHeader('X-Nextcloud-Federation')) {
- return false;
- }
- $userSession = Server::get(\OC\User\Session::class);
- if (OC_User::handleApacheAuth()) {
- return true;
- }
- if (self::tryAppAPILogin($request)) {
- return true;
- }
- if ($userSession->tryTokenLogin($request)) {
- return true;
- }
- if (isset($_COOKIE['nc_username'])
- && isset($_COOKIE['nc_token'])
- && isset($_COOKIE['nc_session_id'])
- && $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
- return true;
- }
- if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
- return true;
- }
- return false;
- }
-
- protected static function handleAuthHeaders(): void {
- //copy http auth headers for apache+php-fcgid work around
- if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
- $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
- }
-
- // Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
- $vars = [
- 'HTTP_AUTHORIZATION', // apache+php-cgi work around
- 'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
- ];
- foreach ($vars as $var) {
- if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
- $credentials = explode(':', base64_decode($matches[1]), 2);
- if (count($credentials) === 2) {
- $_SERVER['PHP_AUTH_USER'] = $credentials[0];
- $_SERVER['PHP_AUTH_PW'] = $credentials[1];
- break;
- }
- }
- }
- }
-
- protected static function tryAppAPILogin(OCP\IRequest $request): bool {
- if (!$request->getHeader('AUTHORIZATION-APP-API')) {
- return false;
- }
- $appManager = Server::get(OCP\App\IAppManager::class);
- if (!$appManager->isEnabledForAnyone('app_api')) {
- return false;
- }
- try {
- $appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
- return $appAPIService->validateExAppRequestToNC($request);
- } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
- return false;
- }
- }
-}
+require_once __DIR__ . '/OC.php';
-OC::init();
+\OC::boot();
+\OC::init();
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 373a697a327a5..102e6a8b9cb34 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -368,7 +368,7 @@ protected function addMissingDefaultValues(array $userData, array $defaultUserDa
}
protected function updateVerificationStatus(IAccount $updatedAccount, array $oldData): void {
- static $propertiesVerifiableByLookupServer = [
+ $propertiesVerifiableByLookupServer = [
self::PROPERTY_TWITTER,
self::PROPERTY_FEDIVERSE,
self::PROPERTY_WEBSITE,
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index 6eddb2b2c41e6..9bf5909cce99b 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -54,6 +54,9 @@ class AppManager implements IAppManager {
/** @var string[] $appId => $enabled */
private array $enabledAppsCache = [];
+ /** @var array $appId => approot information */
+ private array $appsDirCache = [];
+
/** @var string[]|null */
private ?array $shippedApps = null;
@@ -743,11 +746,9 @@ public function findAppInDirectories(string $appId, bool $ignoreCache = false) {
if ($sanitizedAppId !== $appId) {
return false;
}
- // FIXME replace by a property or a cache
- static $app_dir = [];
- if (isset($app_dir[$appId]) && !$ignoreCache) {
- return $app_dir[$appId];
+ if (isset($this->appsDirCache[$appId]) && !$ignoreCache) {
+ return $this->appsDirCache[$appId];
}
$possibleApps = [];
@@ -761,7 +762,7 @@ public function findAppInDirectories(string $appId, bool $ignoreCache = false) {
return false;
} elseif (count($possibleApps) === 1) {
$dir = array_shift($possibleApps);
- $app_dir[$appId] = $dir;
+ $this->appsDirCache[$appId] = $dir;
return $dir;
} else {
$versionToLoad = [];
@@ -778,7 +779,7 @@ public function findAppInDirectories(string $appId, bool $ignoreCache = false) {
if (!isset($versionToLoad['dir'])) {
return false;
}
- $app_dir[$appId] = $versionToLoad['dir'];
+ $this->appsDirCache[$appId] = $versionToLoad['dir'];
return $versionToLoad['dir'];
}
}
diff --git a/lib/private/App/PlatformRepository.php b/lib/private/App/PlatformRepository.php
index faed8b07feb50..1a1e27783a1dc 100644
--- a/lib/private/App/PlatformRepository.php
+++ b/lib/private/App/PlatformRepository.php
@@ -127,7 +127,7 @@ public function findLibrary(string $name): ?string {
return null;
}
- private static string $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
+ private const string MODIFIER_REGEX = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
/**
* Normalizes a version string to be able to perform comparisons on it
@@ -154,16 +154,16 @@ public function normalizeVersion(string $version, ?string $fullVersion = null):
return 'dev-' . substr($version, 4);
}
// match classical versioning
- if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
+ if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?' . self::MODIFIER_REGEX . '$}i', $version, $matches)) {
$version = $matches[1]
. (!empty($matches[2]) ? $matches[2] : '.0')
. (!empty($matches[3]) ? $matches[3] : '.0')
. (!empty($matches[4]) ? $matches[4] : '.0');
$index = 5;
- } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { // match date-based versioning
+ } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::MODIFIER_REGEX . '$}i', $version, $matches)) { // match date-based versioning
$version = preg_replace('{\D}', '-', $matches[1]);
$index = 2;
- } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
+ } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?' . self::MODIFIER_REGEX . '$}i', $version, $matches)) {
$version = $matches[1]
. (!empty($matches[2]) ? $matches[2] : '.0')
. (!empty($matches[3]) ? $matches[3] : '.0')
diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php
index 49b24ec8b34f9..5417095f44bee 100644
--- a/lib/private/Files/Cache/Storage.php
+++ b/lib/private/Files/Cache/Storage.php
@@ -28,18 +28,12 @@
* @package OC\Files\Cache
*/
class Storage {
- private static ?StorageGlobal $globalCache = null;
-
private string $storageId;
private int $numericId;
public static function getGlobalCache(): StorageGlobal {
- if (is_null(self::$globalCache)) {
- self::$globalCache = new StorageGlobal(Server::get(IDBConnection::class));
- }
-
- return self::$globalCache;
+ return Server::get(StorageGlobal::class);
}
/**
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index 5cd32fdfe0e08..ce6e479f6522f 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -95,6 +95,8 @@ class SetupManager implements ISetupManager {
private const SETUP_WITH_CHILDREN = 1;
private const SETUP_WITHOUT_CHILDREN = 0;
+ private bool $updatingProviders = false;
+
public function __construct(
private IEventLogger $eventLogger,
private MountProviderCollection $mountProviderCollection,
@@ -245,11 +247,10 @@ private function updateNonAuthoritativeProviders(IUser $user): void {
}
// prevent recursion loop from when getting mounts from providers ends up setting up the filesystem
- static $updatingProviders = false;
- if ($updatingProviders) {
+ if ($this->updatingProviders) {
return;
}
- $updatingProviders = true;
+ $this->updatingProviders = true;
$providers = $this->mountProviderCollection->getProviders();
$nonAuthoritativeProviders = array_filter(
@@ -265,7 +266,7 @@ private function updateNonAuthoritativeProviders(IUser $user): void {
$this->userMountCache->registerMounts($user, $mount, $providerNames);
$this->usersMountsUpdated[$user->getUID()] = true;
- $updatingProviders = false;
+ $this->updatingProviders = false;
}
#[Override]
diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php
index 696232da64fa0..bb6848bc4fb11 100644
--- a/lib/private/Files/Storage/Common.php
+++ b/lib/private/Files/Storage/Common.php
@@ -74,6 +74,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage,
private ?LoggerInterface $logger = null;
private ?IFilenameValidator $filenameValidator = null;
+ private ?CacheDependencies $cacheDependencies = null;
+
public function __construct(array $parameters) {
}
@@ -304,11 +306,10 @@ public function hasUpdated(string $path, int $time): bool {
}
protected function getCacheDependencies(): CacheDependencies {
- static $dependencies = null;
- if (!$dependencies) {
- $dependencies = Server::get(CacheDependencies::class);
+ if ($this->cacheDependencies === null) {
+ $this->cacheDependencies = Server::get(CacheDependencies::class);
}
- return $dependencies;
+ return $this->cacheDependencies;
}
public function getCache(string $path = '', ?IStorage $storage = null): ICache {
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index 1611d428f3016..bc0a2a47d3aa2 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -7,6 +7,7 @@
*/
namespace OC\Memcache;
+use OC\RedisFactory;
use OCP\IMemcacheTTL;
use OCP\Server;
@@ -37,24 +38,18 @@ class Redis extends Cache implements IMemcacheTTL {
private const MAX_TTL = 30 * 24 * 60 * 60; // 1 month
- /**
- * @var \Redis|\RedisCluster $cache
- */
- private static $cache = null;
+ private \Redis|\RedisCluster $cache;
public function __construct($prefix = '', string $logFile = '') {
parent::__construct($prefix);
+ $this->cache = \OCP\Server::get(RedisFactory::class)->getInstance();
}
/**
- * @return \Redis|\RedisCluster|null
* @throws \Exception
*/
- public function getCache() {
- if (is_null(self::$cache)) {
- self::$cache = Server::get('RedisFactory')->getInstance();
- }
- return self::$cache;
+ public function getCache(): \Redis|\RedisCluster {
+ return $this->cache;
}
public function get($key) {
diff --git a/lib/private/NaturalSort.php b/lib/private/NaturalSort.php
index 240d4de637a1e..d367cc9d6e960 100644
--- a/lib/private/NaturalSort.php
+++ b/lib/private/NaturalSort.php
@@ -11,7 +11,6 @@
use Psr\Log\LoggerInterface;
class NaturalSort {
- private static $instance;
private $collator;
private $cache = [];
@@ -113,10 +112,7 @@ public function compare($a, $b) {
* Returns a singleton
* @return NaturalSort instance
*/
- public static function getInstance() {
- if (!isset(self::$instance)) {
- self::$instance = new NaturalSort();
- }
- return self::$instance;
+ public static function getInstance(): NaturalSort {
+ return \OCP\Server::get(NaturalSort::class);
}
}
diff --git a/lib/private/RedisFactory.php b/lib/private/RedisFactory.php
index 4c8160d81d1f1..fb90e59a5725c 100644
--- a/lib/private/RedisFactory.php
+++ b/lib/private/RedisFactory.php
@@ -131,15 +131,14 @@ private function getSslContext(array $config): ?array {
}
public function getInstance(): \Redis|\RedisCluster {
- if (!$this->isAvailable()) {
- throw new \Exception('Redis support is not available');
- }
if ($this->instance === null) {
+ if (!$this->isAvailable()) {
+ throw new \Exception('Redis support is not available');
+ }
$this->create();
- }
-
- if ($this->instance === null) {
- throw new \Exception('Redis support is not available');
+ if ($this->instance === null) {
+ throw new \Exception('Redis support is not available');
+ }
}
return $this->instance;
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 05af431c00f12..729a60efd04fe 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -60,7 +60,6 @@
use OC\Files\Config\UserMountCacheListener;
use OC\Files\Conversion\ConversionManager;
use OC\Files\FilenameValidator;
-use OC\Files\Filesystem;
use OC\Files\Lock\LockManager;
use OC\Files\Mount\CacheMountProvider;
use OC\Files\Mount\LocalHomeMountProvider;
@@ -440,12 +439,11 @@ public function __construct(
});
$this->registerAlias(IFileAccess::class, FileAccess::class);
$this->registerService('RootFolder', function (ContainerInterface $c) {
- $manager = Filesystem::getMountManager();
$view = new View();
/** @var IUserSession $userSession */
$userSession = $c->get(IUserSession::class);
$root = new Root(
- $manager,
+ $c->get(\OC\Files\Mount\Manager::class),
$view,
$userSession->getUser(),
$c->get(IUserMountCache::class),
@@ -671,10 +669,7 @@ public function __construct(
});
$this->registerAlias(ICacheFactory::class, Factory::class);
- $this->registerService('RedisFactory', function (Server $c) {
- $systemConfig = $c->get(SystemConfig::class);
- return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
- });
+ $this->registerDeprecatedAlias('RedisFactory', RedisFactory::class);
$this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
$l10n = $this->get(IFactory::class)->get('lib');
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index efdbd1cfd6c57..7ba40f6578b97 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -72,7 +72,7 @@ public function __construct(
$this->l10n = $l10nFactory->get('lib');
}
- protected static array $dbSetupClasses = [
+ private const array DB_SETUP_CLASSES = [
'mysql' => MySQL::class,
'pgsql' => PostgreSQL::class,
'oci' => OCI::class,
@@ -334,13 +334,13 @@ public function install(array $options, ?IOutput $output = null): array {
$options['directory'] = \OC::$SERVERROOT . '/data';
}
- if (!isset(self::$dbSetupClasses[$dbType])) {
+ if (!isset(self::DB_SETUP_CLASSES[$dbType])) {
$dbType = 'sqlite';
}
$dataDir = htmlspecialchars_decode($options['directory']);
- $class = self::$dbSetupClasses[$dbType];
+ $class = self::DB_SETUP_CLASSES[$dbType];
/** @var AbstractDatabase $dbSetup */
$dbSetup = new $class($l, $this->config, $this->logger, $this->random);
$error = array_merge($error, $dbSetup->validate($options));
diff --git a/lib/private/TaskProcessing/Db/Task.php b/lib/private/TaskProcessing/Db/Task.php
index 3ac4facf97eab..7b30de57d5284 100644
--- a/lib/private/TaskProcessing/Db/Task.php
+++ b/lib/private/TaskProcessing/Db/Task.php
@@ -76,12 +76,12 @@ class Task extends Entity {
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at', 'allow_cleanup', 'user_facing_error_message', 'include_watermark'];
+ public const array COLUMNS = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at', 'allow_cleanup', 'user_facing_error_message', 'include_watermark'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt', 'allowCleanup', 'userFacingErrorMessage', 'includeWatermark'];
+ public const array FIELDS = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt', 'allowCleanup', 'userFacingErrorMessage', 'includeWatermark'];
public function __construct() {
@@ -109,9 +109,9 @@ public function __construct() {
}
public function toRow(): array {
- return array_combine(self::$columns, array_map(function ($field) {
+ return array_combine(self::COLUMNS, array_map(function ($field) {
return $this->{'get' . ucfirst($field)}();
- }, self::$fields));
+ }, self::FIELDS));
}
public static function fromPublicTask(OCPTask $task): self {
diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php
index f62bb41be3b77..08afdfd923e0a 100644
--- a/lib/private/TaskProcessing/Db/TaskMapper.php
+++ b/lib/private/TaskProcessing/Db/TaskMapper.php
@@ -38,7 +38,7 @@ public function __construct(
*/
public function find(int $id): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
return $this->findEntity($qb);
@@ -53,7 +53,7 @@ public function find(int $id): Task {
*/
public function findOldestScheduledByType(array $taskTypes, array $taskIdsToIgnore): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('status', $qb->createPositionalParameter(\OCP\TaskProcessing\Task::STATUS_SCHEDULED, IQueryBuilder::PARAM_INT)))
->setMaxResults(1)
@@ -85,7 +85,7 @@ public function findOldestScheduledByType(array $taskTypes, array $taskIdsToIgno
*/
public function findByIdAndUser(int $id, ?string $userId): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
if ($userId === null) {
@@ -105,7 +105,7 @@ public function findByIdAndUser(int $id, ?string $userId): Task {
*/
public function findByUserAndTaskType(?string $userId, ?string $taskType = null, ?string $customId = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
if ($taskType !== null) {
@@ -126,7 +126,7 @@ public function findByUserAndTaskType(?string $userId, ?string $taskType = null,
*/
public function findUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
@@ -151,7 +151,7 @@ public function findTasks(
?string $userId, ?string $taskType = null, ?string $appId = null, ?string $customId = null,
?int $status = null, ?int $scheduleAfter = null, ?int $endedBefore = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName);
// empty string: no userId filter
@@ -205,7 +205,7 @@ public function deleteOlderThan(int $timeout, bool $force = false): int {
*/
public function getTasksToCleanup(int $timeout, bool $force = false): \Generator {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
if (!$force) {
@@ -243,7 +243,7 @@ public function lockTask(Entity $entity): int {
*/
public function findNOldestScheduledByType(array $taskTypes, array $taskIdsToIgnore, int $numberOfTasks) {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('status', $qb->createPositionalParameter(\OCP\TaskProcessing\Task::STATUS_SCHEDULED, IQueryBuilder::PARAM_INT)))
->setMaxResults($numberOfTasks)
diff --git a/lib/private/TextProcessing/Db/Task.php b/lib/private/TextProcessing/Db/Task.php
index d4ebc19e74a7e..1bb018415ee9c 100644
--- a/lib/private/TextProcessing/Db/Task.php
+++ b/lib/private/TextProcessing/Db/Task.php
@@ -46,13 +46,12 @@ class Task extends Entity {
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at'];
+ public const array COLUMNS = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt'];
-
+ public const array FIELDS = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt'];
public function __construct() {
// add types in constructor
@@ -69,9 +68,9 @@ public function __construct() {
}
public function toRow(): array {
- return array_combine(self::$columns, array_map(function ($field) {
+ return array_combine(self::COLUMNS, array_map(function ($field) {
return $this->{'get' . ucfirst($field)}();
- }, self::$fields));
+ }, self::FIELDS));
}
public static function fromPublicTask(OCPTask $task): Task {
diff --git a/lib/private/TextProcessing/Db/TaskMapper.php b/lib/private/TextProcessing/Db/TaskMapper.php
index b03e5833958bf..2833a25378d52 100644
--- a/lib/private/TextProcessing/Db/TaskMapper.php
+++ b/lib/private/TextProcessing/Db/TaskMapper.php
@@ -37,7 +37,7 @@ public function __construct(
*/
public function find(int $id): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
return $this->findEntity($qb);
@@ -53,7 +53,7 @@ public function find(int $id): Task {
*/
public function findByIdAndUser(int $id, ?string $userId): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
if ($userId === null) {
@@ -73,7 +73,7 @@ public function findByIdAndUser(int $id, ?string $userId): Task {
*/
public function findUserTasksByApp(string $userId, string $appId, ?string $identifier = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php
index 3fe45ce55ece2..b6021246c0199 100644
--- a/lib/private/TextProcessing/Manager.php
+++ b/lib/private/TextProcessing/Manager.php
@@ -44,7 +44,10 @@ class Manager implements IManager {
/** @var ?IProvider[] */
private ?array $providers = null;
- private static array $taskProcessingCompatibleTaskTypes = [
+ /**
+ * @var array
+ */
+ private const array COMPATIBLE_TASK_TYPES = [
FreePromptTaskType::class => TextToText::ID,
HeadlineTaskType::class => TextToTextHeadline::ID,
SummaryTaskType::class => TextToTextSummary::ID,
@@ -91,7 +94,7 @@ public function getProviders(): array {
public function hasProviders(): bool {
// check if task processing equivalent types are available
$taskTaskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
- foreach (self::$taskProcessingCompatibleTaskTypes as $textTaskTypeClass => $taskTaskTypeId) {
+ foreach (self::COMPATIBLE_TASK_TYPES as $textTaskTypeClass => $taskTaskTypeId) {
if (isset($taskTaskTypes[$taskTaskTypeId])) {
return true;
}
@@ -115,7 +118,7 @@ public function getAvailableTaskTypes(): array {
// check if task processing equivalent types are available
$taskTaskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
- foreach (self::$taskProcessingCompatibleTaskTypes as $textTaskTypeClass => $taskTaskTypeId) {
+ foreach (self::COMPATIBLE_TASK_TYPES as $textTaskTypeClass => $taskTaskTypeId) {
if (isset($taskTaskTypes[$taskTaskTypeId])) {
$tasks[$textTaskTypeClass] = true;
}
@@ -134,9 +137,9 @@ public function canHandleTask(OCPTask $task): bool {
public function runTask(OCPTask $task): string {
// try to run a task processing task if possible
$taskTypeClass = $task->getType();
- if (isset(self::$taskProcessingCompatibleTaskTypes[$taskTypeClass]) && isset($this->taskProcessingManager->getAvailableTaskTypes()[self::$taskProcessingCompatibleTaskTypes[$taskTypeClass]])) {
+ if (isset(self::COMPATIBLE_TASK_TYPES[$taskTypeClass]) && isset($this->taskProcessingManager->getAvailableTaskTypes()[self::COMPATIBLE_TASK_TYPES[$taskTypeClass]])) {
try {
- $taskProcessingTaskTypeId = self::$taskProcessingCompatibleTaskTypes[$taskTypeClass];
+ $taskProcessingTaskTypeId = self::COMPATIBLE_TASK_TYPES[$taskTypeClass];
$taskProcessingTask = new \OCP\TaskProcessing\Task(
$taskProcessingTaskTypeId,
['input' => $task->getInput()],
@@ -222,8 +225,8 @@ public function scheduleTask(OCPTask $task): void {
$task->setStatus(OCPTask::STATUS_SCHEDULED);
$providers = $this->getPreferredProviders($task);
$equivalentTaskProcessingTypeAvailable = (
- isset(self::$taskProcessingCompatibleTaskTypes[$task->getType()])
- && isset($this->taskProcessingManager->getAvailableTaskTypes()[self::$taskProcessingCompatibleTaskTypes[$task->getType()]])
+ isset(self::COMPATIBLE_TASK_TYPES[$task->getType()])
+ && isset($this->taskProcessingManager->getAvailableTaskTypes()[self::COMPATIBLE_TASK_TYPES[$task->getType()]])
);
if (count($providers) === 0 && !$equivalentTaskProcessingTypeAvailable) {
throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
diff --git a/lib/private/TextToImage/Db/Task.php b/lib/private/TextToImage/Db/Task.php
index 48a8bcc6c407d..a3103a8e103fa 100644
--- a/lib/private/TextToImage/Db/Task.php
+++ b/lib/private/TextToImage/Db/Task.php
@@ -49,12 +49,12 @@ class Task extends Entity {
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at'];
+ public const array COLUMNS = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt'];
+ public const array FIELDS = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt'];
public function __construct() {
@@ -71,9 +71,9 @@ public function __construct() {
}
public function toRow(): array {
- return array_combine(self::$columns, array_map(function ($field) {
+ return array_combine(self::COLUMNS, array_map(function ($field) {
return $this->{'get' . ucfirst($field)}();
- }, self::$fields));
+ }, self::FIELDS));
}
public static function fromPublicTask(OCPTask $task): Task {
diff --git a/lib/private/TextToImage/Db/TaskMapper.php b/lib/private/TextToImage/Db/TaskMapper.php
index 37f492e14cf3b..31af53c326798 100644
--- a/lib/private/TextToImage/Db/TaskMapper.php
+++ b/lib/private/TextToImage/Db/TaskMapper.php
@@ -38,7 +38,7 @@ public function __construct(
*/
public function find(int $id): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
return $this->findEntity($qb);
@@ -54,7 +54,7 @@ public function find(int $id): Task {
*/
public function findByIdAndUser(int $id, ?string $userId): Task {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
if ($userId === null) {
@@ -74,7 +74,7 @@ public function findByIdAndUser(int $id, ?string $userId): Task {
*/
public function findUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select(Task::$columns)
+ $qb->select(Task::COLUMNS)
->from($this->tableName)
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 76e5fda71fb11..17370fa3c1691 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -46,6 +46,14 @@ class OC_App {
public const supportedApp = 300;
public const officialApp = 200;
+ /**
+ * @internal
+ */
+ public static function reset(): void {
+ self::$altLogin = [];
+ self::$alreadyRegistered = [];
+ }
+
/**
* clean the appId
*
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
index 3dc7470811dbc..09110728b7a87 100644
--- a/lib/private/legacy/OC_Helper.php
+++ b/lib/private/legacy/OC_Helper.php
@@ -44,6 +44,14 @@ class OC_Helper {
private static ?ICacheFactory $cacheFactory = null;
private static ?bool $quotaIncludeExternalStorage = null;
+ /**
+ * @internal
+ */
+ public static function reset(): void {
+ self::$cacheFactory = null;
+ self::$quotaIncludeExternalStorage = null;
+ }
+
/**
* Recursive copying of folders
* @param string $src source folder
diff --git a/lib/private/legacy/OC_Hook.php b/lib/private/legacy/OC_Hook.php
index 888057a7aca23..0def846b7cfd8 100644
--- a/lib/private/legacy/OC_Hook.php
+++ b/lib/private/legacy/OC_Hook.php
@@ -114,6 +114,7 @@ public static function clear($signalClass = '', $signalName = '') {
}
} else {
self::$registered = [];
+ self::$thrownExceptions = [];
}
}
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
index ddd426da942e4..b15e18e8b1650 100644
--- a/lib/private/legacy/OC_User.php
+++ b/lib/private/legacy/OC_User.php
@@ -51,7 +51,7 @@
* logout()
*/
class OC_User {
- private static $_setupedBackends = [];
+ public static $_setupedBackends = [];
// bool, stores if a user want to access a resource anonymously, e.g if they open a public link
private static $incognitoMode = false;
diff --git a/lib/public/Util.php b/lib/public/Util.php
index 0610460fc2237..0ecfc95075b4f 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -412,24 +412,13 @@ public static function emitHook($signalclass, $signalname, $params = []) {
return \OC_Hook::emit($signalclass, $signalname, $params);
}
- /**
- * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare
- * multiple Template elements which invoke `callRegister`. If the value
- * would not be cached these unit-tests would fail.
- * @var string
- */
- private static $token = '';
-
/**
* Register an get/post call. This is important to prevent CSRF attacks
* @since 4.5.0
* @deprecated 32.0.0 directly use CsrfTokenManager instead
*/
public static function callRegister() {
- if (self::$token === '') {
- self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
- }
- return self::$token;
+ return \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
}
/**
diff --git a/psalm.xml b/psalm.xml
index 9b029ce31d497..adfaefffdd9fe 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -18,6 +18,7 @@
+