Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9c6206c2f | |||
| c7fca0071b | |||
| dbc41af77f | |||
| db558a7394 |
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
namespace OCA\Theming;
|
||||
|
||||
use OC\Kernel\Kernel;
|
||||
use OCA\Theming\AppInfo\Application;
|
||||
use OCA\Theming\Service\BackgroundService;
|
||||
use OCP\App\AppPathNotFoundException;
|
||||
@@ -395,7 +396,7 @@ class ThemingDefaults extends \OC_Defaults {
|
||||
}
|
||||
$route = $this->urlGenerator->linkToRoute('theming.Theming.getManifest', ['app' => $app ]);
|
||||
}
|
||||
if (str_starts_with($image, 'filetypes/') && file_exists(\OC::$SERVERROOT . '/core/img/' . $image)) {
|
||||
if (str_starts_with($image, 'filetypes/') && file_exists(Kernel::getInstance()->getServerRoot() . '/core/img/' . $image)) {
|
||||
$route = $this->urlGenerator->linkToRoute('theming.Icon.getThemedIcon', ['app' => $app, 'image' => $image]);
|
||||
}
|
||||
|
||||
|
||||
+8
-110
@@ -2,120 +2,18 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Server;
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
use OC\Kernel\ConsoleKernel;
|
||||
|
||||
require_once __DIR__ . '/lib/versioncheck.php';
|
||||
require_once __DIR__ . '/lib/private/Kernel/Kernel.php';
|
||||
require_once __DIR__ . '/lib/private/Kernel/ConsoleKernel.php';
|
||||
|
||||
use OC\Console\Application;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\IRequest;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
|
||||
define('OC_CONSOLE', 1);
|
||||
|
||||
function exceptionHandler($exception) {
|
||||
echo 'An unhandled exception has been thrown:' . PHP_EOL;
|
||||
echo $exception;
|
||||
exit(1);
|
||||
}
|
||||
try {
|
||||
require_once __DIR__ . '/lib/base.php';
|
||||
|
||||
// set to run indefinitely if needed
|
||||
if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
|
||||
@set_time_limit(0);
|
||||
}
|
||||
|
||||
if (!OC::$CLI) {
|
||||
echo 'This script can be run from the command line only' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$config = Server::get(IConfig::class);
|
||||
set_exception_handler('exceptionHandler');
|
||||
|
||||
if (!function_exists('posix_getuid')) {
|
||||
echo 'The posix extensions are required - see https://www.php.net/manual/en/book.posix.php' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$user = posix_getuid();
|
||||
$configUser = fileowner(OC::$configDir . 'config.php');
|
||||
if ($user !== $configUser) {
|
||||
echo 'Console has to be executed with the user that owns the file config/config.php' . PHP_EOL;
|
||||
echo 'Current user id: ' . $user . PHP_EOL;
|
||||
echo 'Owner id of config.php: ' . $configUser . PHP_EOL;
|
||||
echo "Try adding 'sudo -u #" . $configUser . "' to the beginning of the command (without the single quotes)" . PHP_EOL;
|
||||
echo "If running with 'docker exec' try adding the option '-u " . $configUser . "' to the docker command (without the single quotes)" . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$oldWorkingDir = getcwd();
|
||||
if ($oldWorkingDir === false) {
|
||||
echo 'This script can be run from the Nextcloud root directory only.' . PHP_EOL;
|
||||
echo "Can't determine current working dir - the script will continue to work but be aware of the above fact." . PHP_EOL;
|
||||
} elseif ($oldWorkingDir !== __DIR__ && !chdir(__DIR__)) {
|
||||
echo 'This script can be run from the Nextcloud root directory only.' . PHP_EOL;
|
||||
echo "Can't change to Nextcloud root directory." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!(function_exists('pcntl_signal') && function_exists('pcntl_signal_dispatch')) && !in_array('--no-warnings', $argv)) {
|
||||
echo 'The process control (PCNTL) extensions are required in case you want to interrupt long running commands - see https://www.php.net/manual/en/book.pcntl.php' . PHP_EOL;
|
||||
echo "Additionally the function 'pcntl_signal' and 'pcntl_signal_dispatch' need to be enabled in your php.ini." . PHP_EOL;
|
||||
}
|
||||
|
||||
$eventLogger = Server::get(IEventLogger::class);
|
||||
$eventLogger->start('console:build_application', 'Build Application instance and load commands');
|
||||
|
||||
$application = Server::get(Application::class);
|
||||
/* base.php will have removed eventual debug options from argv in $_SERVER */
|
||||
$argv = $_SERVER['argv'];
|
||||
$input = new ArgvInput($argv);
|
||||
$output = new ConsoleOutput();
|
||||
$application->loadCommands($input, $output);
|
||||
|
||||
$eventLogger->end('console:build_application');
|
||||
$eventLogger->start('console:run', 'Run the command');
|
||||
|
||||
$application->setAutoExit(false);
|
||||
$exitCode = $application->run($input);
|
||||
|
||||
$eventLogger->end('console:run');
|
||||
|
||||
$profiler = Server::get(IProfiler::class);
|
||||
if ($profiler->isEnabled()) {
|
||||
$eventLogger->end('runtime');
|
||||
$profile = $profiler->collect(Server::get(IRequest::class), new Response());
|
||||
$profile->setMethod('occ');
|
||||
$profile->setUrl(implode(' ', $argv));
|
||||
$profiler->saveProfile($profile);
|
||||
|
||||
$urlGenerator = Server::get(IURLGenerator::class);
|
||||
$url = $urlGenerator->linkToRouteAbsolute('profiler.main.profiler', [
|
||||
'profiler' => 'db',
|
||||
'token' => $profile->getToken(),
|
||||
]);
|
||||
$output->getErrorOutput()->writeln('Profiler output available at ' . $url);
|
||||
}
|
||||
|
||||
if ($exitCode > 255) {
|
||||
$exitCode = 255;
|
||||
}
|
||||
|
||||
exit($exitCode);
|
||||
} catch (Exception $ex) {
|
||||
exceptionHandler($ex);
|
||||
} catch (Error $ex) {
|
||||
exceptionHandler($ex);
|
||||
}
|
||||
(new ConsoleKernel())
|
||||
->boot()
|
||||
->run($_SERVER['argv']);
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace OC\Core\Controller;
|
||||
|
||||
use OC\IntegrityCheck\Checker;
|
||||
use OC\Setup;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Server;
|
||||
@@ -31,7 +35,7 @@ class SetupController {
|
||||
$this->autoConfigFile = \OC::$configDir . 'autoconfig.php';
|
||||
}
|
||||
|
||||
public function run(array $post): void {
|
||||
public function run(array $post): Response {
|
||||
// Check for autosetup:
|
||||
$post = $this->loadAutoConfig($post);
|
||||
$opts = $this->setupHelper->getSystemInfo();
|
||||
@@ -45,8 +49,7 @@ class SetupController {
|
||||
}
|
||||
|
||||
if (!$this->setupHelper->canInstallFileExists()) {
|
||||
$this->displaySetupForbidden();
|
||||
return;
|
||||
return $this->displaySetupForbidden();
|
||||
}
|
||||
|
||||
if (isset($post['install']) && $post['install'] == 'true') {
|
||||
@@ -56,21 +59,21 @@ class SetupController {
|
||||
|
||||
if (count($e) > 0) {
|
||||
$options = array_merge($opts, $post, $errors);
|
||||
$this->display($options);
|
||||
return $this->display($options);
|
||||
} else {
|
||||
$this->finishSetup();
|
||||
return $this->finishSetup();
|
||||
}
|
||||
} else {
|
||||
$options = array_merge($opts, $post);
|
||||
$this->display($options);
|
||||
return $this->display($options);
|
||||
}
|
||||
}
|
||||
|
||||
private function displaySetupForbidden(): void {
|
||||
$this->templateManager->printGuestPage('', 'installation_forbidden');
|
||||
private function displaySetupForbidden(): TemplateResponse {
|
||||
return new TemplateResponse('', 'installation_forbidden', [], TemplateResponse::RENDER_AS_GUEST);
|
||||
}
|
||||
|
||||
public function display(array $post): void {
|
||||
public function display(array $post): TemplateResponse {
|
||||
$defaults = [
|
||||
'adminlogin' => '',
|
||||
'adminpass' => '',
|
||||
@@ -103,10 +106,10 @@ class SetupController {
|
||||
'adminDBConfiguration' => $this->urlGenerator->linkToDocs('admin-db-configuration'),
|
||||
]);
|
||||
|
||||
$this->templateManager->printGuestPage('', 'installation');
|
||||
return new TemplateResponse('', 'installation', [], TemplateResponse::RENDER_AS_GUEST);
|
||||
}
|
||||
|
||||
private function finishSetup(): void {
|
||||
private function finishSetup(): RedirectResponse {
|
||||
if (file_exists($this->autoConfigFile)) {
|
||||
unlink($this->autoConfigFile);
|
||||
}
|
||||
@@ -116,8 +119,7 @@ class SetupController {
|
||||
$this->templateManager->printGuestPage('', 'installation_incomplete');
|
||||
}
|
||||
|
||||
header('Location: ' . Server::get(IURLGenerator::class)->getAbsoluteURL('index.php/core/apps/recommended'));
|
||||
exit();
|
||||
return new RedirectResponse(Server::get(IURLGenerator::class)->getAbsoluteURL('index.php/core/apps/recommended'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,99 +10,17 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/lib/versioncheck.php';
|
||||
|
||||
use OC\ServiceUnavailableException;
|
||||
use OC\User\LoginException;
|
||||
use OCP\HintException;
|
||||
use OC\Kernel\HttpKernel;
|
||||
use OCP\AppFramework\Http\IOutput;
|
||||
use OCP\IRequest;
|
||||
use OCP\Security\Bruteforce\MaxDelayReached;
|
||||
use OCP\Server;
|
||||
use OCP\Template\ITemplateManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
try {
|
||||
require_once __DIR__ . '/lib/base.php';
|
||||
require_once __DIR__ . '/lib/private/Kernel/Kernel.php';
|
||||
require_once __DIR__ . '/lib/private/Kernel/HttpKernel.php';
|
||||
|
||||
OC::handleRequest();
|
||||
} catch (ServiceUnavailableException $ex) {
|
||||
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
|
||||
'app' => 'index',
|
||||
'exception' => $ex,
|
||||
]);
|
||||
$kernel = (new HttpKernel())
|
||||
->boot();
|
||||
|
||||
//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,
|
||||
]);
|
||||
} 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");
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
|
||||
}
|
||||
$request = $kernel->getServer()->get(IRequest::class);
|
||||
$output = $kernel->getServer()->get(IOutput::class);
|
||||
$response = $kernel->handle($request);
|
||||
$kernel->deliverResponse($request, $response, $output);
|
||||
|
||||
+1
-1211
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ use OC\AppConfig;
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Config\ConfigManager;
|
||||
use OC\DB\MigrationService;
|
||||
use OC\Kernel\Kernel;
|
||||
use OC\Migration\BackgroundRepair;
|
||||
use OCP\Activity\IManager as IActivityManager;
|
||||
use OCP\App\AppPathNotFoundException;
|
||||
@@ -33,6 +34,7 @@ use OCP\INavigationManager;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Server;
|
||||
use OCP\ServerVersion;
|
||||
use OCP\Settings\IManager as ISettingsManager;
|
||||
@@ -186,7 +188,7 @@ class AppManager implements IAppManager {
|
||||
public function getAllAppsInAppsFolders(): array {
|
||||
$apps = [];
|
||||
|
||||
foreach (\OC::$APPSROOTS as $apps_dir) {
|
||||
foreach (Kernel::getInstance()->getAppsRoots() as $apps_dir) {
|
||||
if (!is_readable($apps_dir['path'])) {
|
||||
$this->logger->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
|
||||
continue;
|
||||
@@ -725,7 +727,7 @@ class AppManager implements IAppManager {
|
||||
*/
|
||||
public function getAppWebPath(string $appId): string {
|
||||
if (($dir = $this->findAppInDirectories($appId)) != false) {
|
||||
return \OC::$WEBROOT . $dir['url'] . '/' . $appId;
|
||||
return Kernel::getInstance()->getWebRoot() . $dir['url'] . '/' . $appId;
|
||||
}
|
||||
throw new AppPathNotFoundException('Could not find web path for ' . $appId);
|
||||
}
|
||||
@@ -751,7 +753,7 @@ class AppManager implements IAppManager {
|
||||
}
|
||||
|
||||
$possibleApps = [];
|
||||
foreach (\OC::$APPSROOTS as $dir) {
|
||||
foreach (Kernel::getInstance()->getAppsRoots() as $dir) {
|
||||
if (file_exists($dir['path'] . '/' . $appId)) {
|
||||
$possibleApps[] = $dir;
|
||||
}
|
||||
@@ -928,7 +930,7 @@ class AppManager implements IAppManager {
|
||||
*/
|
||||
private function loadShippedJson(): void {
|
||||
if ($this->shippedApps === null) {
|
||||
$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
|
||||
$shippedJson = Kernel::getInstance()->getServerRoot() . '/core/shipped.json';
|
||||
if (!file_exists($shippedJson)) {
|
||||
throw new \Exception("File not found: $shippedJson");
|
||||
}
|
||||
@@ -1052,7 +1054,7 @@ class AppManager implements IAppManager {
|
||||
$appPath = $this->getAppPath($appId, true);
|
||||
|
||||
$this->clearAppsCache();
|
||||
$l = \OC::$server->getL10N('core');
|
||||
$l = Server::get(IFactory::class)->get('core');
|
||||
$appData = $this->getAppInfo($appId, false, $l->getLanguageCode());
|
||||
if ($appData === null) {
|
||||
throw new AppPathNotFoundException('Could not find ' . $appId);
|
||||
|
||||
@@ -15,13 +15,13 @@ use OC\Profiler\RoutingDataCollector;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\ICallbackResponse;
|
||||
use OCP\AppFramework\Http\IOutput;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\HintException;
|
||||
use OCP\IRequest;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OCP\Server;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
|
||||
/**
|
||||
* Entry point for every request in your app. You can consider this as your
|
||||
@@ -87,7 +87,7 @@ class App {
|
||||
string $methodName,
|
||||
DIContainer $container,
|
||||
?array $urlParams = null,
|
||||
): void {
|
||||
): Response {
|
||||
/** @var IProfiler $profiler */
|
||||
$profiler = $container->get(IProfiler::class);
|
||||
$eventLogger = $container->get(IEventLogger::class);
|
||||
@@ -118,7 +118,7 @@ class App {
|
||||
// first try $controllerName then go for \OCA\AppName\Controller\$controllerName
|
||||
try {
|
||||
$controller = $container->get($controllerName);
|
||||
} catch (QueryException $e) {
|
||||
} catch (ContainerExceptionInterface $e) {
|
||||
if (str_contains($controllerName, '\\Controller\\')) {
|
||||
// This is from a global registered app route that is not enabled.
|
||||
[/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3);
|
||||
@@ -131,7 +131,7 @@ class App {
|
||||
$appNameSpace = self::buildAppNamespace($appName);
|
||||
}
|
||||
$controllerName = $appNameSpace . '\\Controller\\' . $controllerName;
|
||||
$controller = $container->query($controllerName);
|
||||
$controller = $container->get($controllerName);
|
||||
}
|
||||
|
||||
$eventLogger->end('app:controller:load');
|
||||
@@ -145,53 +145,20 @@ class App {
|
||||
|
||||
$eventLogger->start('app:controller:run', 'Run app controller');
|
||||
|
||||
[
|
||||
$httpHeaders,
|
||||
$responseHeaders,
|
||||
$responseCookies,
|
||||
$output,
|
||||
$response
|
||||
] = $dispatcher->dispatch($controller, $methodName);
|
||||
$response = $dispatcher->dispatch($controller, $methodName);
|
||||
|
||||
$eventLogger->end('app:controller:run');
|
||||
|
||||
$io = $container[IOutput::class];
|
||||
|
||||
if ($profiler->isEnabled()) {
|
||||
$eventLogger->end('runtime');
|
||||
$profile = $profiler->collect($container->get(IRequest::class), $response);
|
||||
$profiler->saveProfile($profile);
|
||||
$io->setHeader('X-Debug-Token:' . $profile->getToken());
|
||||
$io->setHeader('Server-Timing: token;desc="' . $profile->getToken() . '"');
|
||||
}
|
||||
|
||||
if (!is_null($httpHeaders)) {
|
||||
$io->setHeader($httpHeaders);
|
||||
}
|
||||
|
||||
foreach ($responseHeaders as $name => $value) {
|
||||
$io->setHeader($name . ': ' . $value);
|
||||
}
|
||||
|
||||
foreach ($responseCookies as $name => $value) {
|
||||
$expireDate = null;
|
||||
if ($value['expireDate'] instanceof \DateTime) {
|
||||
$expireDate = $value['expireDate']->getTimestamp();
|
||||
}
|
||||
$sameSite = $value['sameSite'] ?? 'Lax';
|
||||
|
||||
$io->setCookie(
|
||||
$name,
|
||||
$value['value'],
|
||||
$expireDate,
|
||||
$container->getServer()->getWebRoot(),
|
||||
null,
|
||||
$container->getServer()->get(IRequest::class)->getServerProtocol() === 'https',
|
||||
true,
|
||||
$sameSite
|
||||
);
|
||||
$response->addHeader('X-Debug-Token', $profile->getToken());
|
||||
$response->addHeader('Server-Timing', 'token;desc="' . $profile->getToken() . '"');
|
||||
}
|
||||
|
||||
return $response;
|
||||
//FIXME dead code below, should be migrated
|
||||
/*
|
||||
* Status 204 does not have a body and no Content Length
|
||||
* Status 304 does not have a body and does not need a Content Length
|
||||
|
||||
@@ -15,12 +15,10 @@ class Http extends BaseHttp {
|
||||
protected $headers;
|
||||
|
||||
/**
|
||||
* @param array $server $_SERVER
|
||||
* @param string $protocolVersion the http version to use defaults to HTTP/1.1
|
||||
*/
|
||||
public function __construct(
|
||||
private $server,
|
||||
private $protocolVersion = 'HTTP/1.1',
|
||||
private string $protocolVersion = 'HTTP/1.1',
|
||||
) {
|
||||
$this->headers = [
|
||||
self::STATUS_CONTINUE => 'Continue',
|
||||
@@ -88,12 +86,10 @@ class Http extends BaseHttp {
|
||||
|
||||
/**
|
||||
* Gets the correct header
|
||||
* @param int Http::CONSTANT $status the constant from the Http class
|
||||
* @param \DateTime $lastModified formatted last modified date
|
||||
* @param string $ETag the etag
|
||||
* @param Http::STATUS_* $status the constant from the Http class
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusHeader($status) {
|
||||
public function getStatusHeader(int $status): string {
|
||||
// we have one change currently for the http 1.0 header that differs
|
||||
// from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND
|
||||
// if this differs any more, we want to create childclasses for this
|
||||
|
||||
@@ -32,7 +32,6 @@ class Dispatcher {
|
||||
* runs the middleware
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Http $protocol,
|
||||
private readonly MiddlewareDispatcher $middlewareDispatcher,
|
||||
private readonly ControllerMethodReflector $reflector,
|
||||
private readonly IRequest $request,
|
||||
@@ -50,15 +49,9 @@ class Dispatcher {
|
||||
* @param Controller $controller the controller which will be called
|
||||
* @param string $methodName the method name which will be called on
|
||||
* the controller
|
||||
* @return array{0: string, 1: array, 2: array, 3: string, 4: Response}
|
||||
* $array[0] contains the http status header as a string,
|
||||
* $array[1] contains response headers as an array,
|
||||
* $array[2] contains response cookies as an array,
|
||||
* $array[3] contains the response output as a string,
|
||||
* $array[4] contains the response object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dispatch(Controller $controller, string $methodName): array {
|
||||
public function dispatch(Controller $controller, string $methodName): Response {
|
||||
try {
|
||||
// prefill reflector with everything that's needed for the
|
||||
// middlewares
|
||||
@@ -112,16 +105,12 @@ class Dispatcher {
|
||||
$response = $this->middlewareDispatcher->afterController(
|
||||
$controller, $methodName, $response);
|
||||
|
||||
// depending on the cache object the headers need to be changed
|
||||
return [
|
||||
$this->protocol->getStatusHeader($response->getStatus()),
|
||||
array_merge($response->getHeaders()),
|
||||
$response->getCookies(),
|
||||
$this->middlewareDispatcher->beforeOutput(
|
||||
$controller, $methodName, $response->render()
|
||||
),
|
||||
$response,
|
||||
];
|
||||
// TODO
|
||||
$this->middlewareDispatcher->beforeOutput(
|
||||
$controller, $methodName, $response->render()
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
|
||||
use OC\DB\QueryBuilder\Sharded\AutoIncrementHandler;
|
||||
use OC\DB\QueryBuilder\Sharded\ShardConnectionManager;
|
||||
use OC\Kernel\Kernel;
|
||||
use OC\SystemConfig;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\Server;
|
||||
@@ -186,7 +187,7 @@ class ConnectionFactory {
|
||||
$name = $this->config->getValue($configPrefix . 'dbname', $this->config->getValue('dbname', self::DEFAULT_DBNAME));
|
||||
|
||||
if ($this->normalizeType($type) === 'sqlite3') {
|
||||
$dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT . '/data');
|
||||
$dataDir = $this->config->getValue('datadirectory', Kernel::getInstance()->getServerRoot() . '/data');
|
||||
$connectionParams['path'] = $dataDir . '/' . $name . '.db';
|
||||
} else {
|
||||
$host = $this->config->getValue($configPrefix . 'dbhost', $this->config->getValue('dbhost', ''));
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
||||
* SPDX-FileContributor: Carl Schwan
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use OC\Console\Application;
|
||||
use OC\SystemConfig;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Throwable;
|
||||
|
||||
class ConsoleKernel extends Kernel {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
set_exception_handler($this->exceptionHandler(...));
|
||||
|
||||
// set to run indefinitely if needed
|
||||
if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
|
||||
@set_time_limit(0);
|
||||
}
|
||||
|
||||
if (!$this->isCli()) {
|
||||
echo 'This script can be run from the command line only' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!function_exists('posix_getuid')) {
|
||||
echo 'The posix extensions are required - see https://www.php.net/manual/en/book.posix.php' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function boot(): self {
|
||||
try {
|
||||
parent::boot();
|
||||
|
||||
$user = posix_getuid();
|
||||
$configUser = fileowner($this->configDir . 'config.php');
|
||||
if ($user !== $configUser) {
|
||||
echo 'Console has to be executed with the user that owns the file config/config.php' . PHP_EOL;
|
||||
echo 'Current user id: ' . $user . PHP_EOL;
|
||||
echo 'Owner id of config.php: ' . $configUser . PHP_EOL;
|
||||
echo "Try adding 'sudo -u #" . $configUser . "' to the beginning of the command (without the single quotes)" . PHP_EOL;
|
||||
echo "If running with 'docker exec' try adding the option '-u " . $configUser . "' to the docker command (without the single quotes)" . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$oldWorkingDir = getcwd();
|
||||
if ($oldWorkingDir === false) {
|
||||
echo 'This script can be run from the Nextcloud root directory only.' . PHP_EOL;
|
||||
echo "Can't determine current working dir - the script will continue to work but be aware of the above fact." . PHP_EOL;
|
||||
} elseif ($oldWorkingDir !== __DIR__ && !chdir(__DIR__)) {
|
||||
echo 'This script can be run from the Nextcloud root directory only.' . PHP_EOL;
|
||||
echo "Can't change to Nextcloud root directory." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function run(array $argv): void {
|
||||
try {
|
||||
if (!(function_exists('pcntl_signal') && function_exists('pcntl_signal_dispatch')) && !in_array('--no-warnings', $argv)) {
|
||||
echo 'The process control (PCNTL) extensions are required in case you want to interrupt long running commands - see https://www.php.net/manual/en/book.pcntl.php' . PHP_EOL;
|
||||
echo "Additionally the function 'pcntl_signal' and 'pcntl_signal_dispatch' need to be enabled in your php.ini." . PHP_EOL;
|
||||
}
|
||||
|
||||
$eventLogger = $this->server->get(IEventLogger::class);
|
||||
$eventLogger->start('console:build_application', 'Build Application instance and load commands');
|
||||
|
||||
$application = $this->server->get(Application::class);
|
||||
/* base.php will have removed eventual debug options from argv in $_SERVER */
|
||||
$input = new ArgvInput($argv);
|
||||
$output = new ConsoleOutput();
|
||||
$application->loadCommands($input, $output);
|
||||
|
||||
$eventLogger->end('console:build_application');
|
||||
$eventLogger->start('console:run', 'Run the command');
|
||||
|
||||
$application->setAutoExit(false);
|
||||
$exitCode = $application->run($input);
|
||||
|
||||
$eventLogger->end('console:run');
|
||||
|
||||
$profiler = $this->server->get(IProfiler::class);
|
||||
if ($profiler->isEnabled()) {
|
||||
$eventLogger->end('runtime');
|
||||
$profile = $profiler->collect($this->server->get(IRequest::class), new Response());
|
||||
$profile->setMethod('occ');
|
||||
$profile->setUrl(implode(' ', $argv));
|
||||
$profiler->saveProfile($profile);
|
||||
|
||||
if ($this->server->get(IAppManager::class)->isEnabledForAnyone('profiler')) {
|
||||
$urlGenerator = $this->server->get(IURLGenerator::class);
|
||||
$url = $urlGenerator->linkToRouteAbsolute('profiler.main.profiler', [
|
||||
'profiler' => 'db',
|
||||
'token' => $profile->getToken(),
|
||||
]);
|
||||
$output->getErrorOutput()->writeln('Profiler output available at ' . $url);
|
||||
}
|
||||
}
|
||||
|
||||
if ($exitCode > 255) {
|
||||
$exitCode = 255;
|
||||
}
|
||||
|
||||
exit($exitCode);
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function exceptionHandler($exception): never {
|
||||
echo 'An unhandled exception has been thrown:' . PHP_EOL;
|
||||
echo $exception;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
protected function setupSession(IRequest $request, IEventLogger $eventLogger): void {
|
||||
}
|
||||
|
||||
public function checkInstalled(SystemConfig $systemConfig): void {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
use OC\AppFramework\Http;
|
||||
use OC\Core\Controller\SetupController;
|
||||
use OC\ServiceUnavailableException;
|
||||
use OC\SystemConfig;
|
||||
use OC\User\DisabledUserException;
|
||||
use OC\User\LoginException;
|
||||
use OC_User;
|
||||
use OC_Util;
|
||||
use OCA\AppAPI\Service\AppAPIService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\ErrorTemplateResponse;
|
||||
use OCP\AppFramework\Http\IOutput;
|
||||
use OCP\AppFramework\Http\NotFoundResponse;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\HintException;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\Bruteforce\MaxDelayReached;
|
||||
use OCP\Server;
|
||||
use OCP\Template\ITemplateManager;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
||||
use Throwable;
|
||||
use function OCP\Log\logger;
|
||||
|
||||
class HttpKernel extends Kernel {
|
||||
public function handle(IRequest $request): Response {
|
||||
try {
|
||||
return $this->doHandle($request);
|
||||
} 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,
|
||||
]);
|
||||
} 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");
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function doHandle(IRequest $request): Response {
|
||||
$this->getContainer()->get(IEventLogger::class)
|
||||
->start('handle_request', 'Handle request');
|
||||
|
||||
// Check if Nextcloud is installed or in maintenance (update) mode
|
||||
if (!$this->getSystemConfig()->getValue('installed', false)) {
|
||||
$this->getContainer()->get(ISession::class)->clear();
|
||||
$controller = $this->getContainer()->get(SetupController::class);
|
||||
return $controller->run($_POST);
|
||||
}
|
||||
|
||||
$request->throwDecodingExceptionIfAny();
|
||||
$requestPath = $request->getRawPathInfo();
|
||||
if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
|
||||
$this->checkMaintenanceMode($this->getSystemConfig());
|
||||
|
||||
if (Util::needUpgrade()) {
|
||||
if (function_exists('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
if (!((bool)$this->getSystemConfig()->getValue('maintenance', false))) {
|
||||
/** @var UpgradePageController $upgradePageController */
|
||||
$upgradePageController = $this->getContainer()->get(UpgradePageController::class);
|
||||
return $upgradePageController->printUpgradePage($this->getSystemConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$appManager = $this->getContainer()->get(\OCP\App\IAppManager::class);
|
||||
|
||||
// Always load authentication apps
|
||||
$appManager->loadApps(['authentication']);
|
||||
$appManager->loadApps(['extended_authentication']);
|
||||
|
||||
// Load minimum set of apps
|
||||
if (!Util::needUpgrade() && !((bool)($this->getSystemConfig()->getValue('maintenance', false)))) {
|
||||
// For logged-in users: Load everything
|
||||
if ($this->getContainer()->get(IUserSession::class)->isLoggedIn()) {
|
||||
$appManager->loadApps();
|
||||
} else {
|
||||
// For guests: Load only filesystem and logging
|
||||
$appManager->loadApps(['filesystem', 'logging']);
|
||||
|
||||
// Don't try to log in 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!Util::needUpgrade()) {
|
||||
$appManager->loadApps(['filesystem', 'logging']);
|
||||
$appManager->loadApps();
|
||||
}
|
||||
return Server::get(\OC\Route\Router::class)->match($request);
|
||||
} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
|
||||
//header('HTTP/1.0 404 Not Found');
|
||||
} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
|
||||
return new ErrorTemplateResponse('', '', status: 405);
|
||||
}
|
||||
|
||||
// Handle WebDAV
|
||||
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
|
||||
// not allowed anymore to prevent people
|
||||
// mounting this root directly.
|
||||
// Users need to mount remote.php/webdav instead.
|
||||
return new ErrorTemplateResponse('', '', status: 405);
|
||||
}
|
||||
|
||||
// Handle requests for JSON or XML
|
||||
$acceptHeader = $request->getHeader('Accept');
|
||||
if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
// 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'])) {
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
$response = new RedirectResponse(Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
|
||||
$response->addHeader('X-User-Id', $userSession->getUser()?->getUID());
|
||||
return $response;
|
||||
} else {
|
||||
// Not handled and not logged in
|
||||
return new RedirectResponse(Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return Server::get(\OC\Route\Router::class)->match('/error/404');
|
||||
} catch (\Exception $e) {
|
||||
if (!$e instanceof MethodNotAllowedException) {
|
||||
logger('core')->emergency($e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
}
|
||||
|
||||
public function checkMaintenanceMode(SystemConfig $systemConfig): ?TemplateResponse {
|
||||
// Allow ajax update script to execute without being stopped
|
||||
if (!((bool)$systemConfig->getValue('maintenance', false)) || $this->getSubUri() === '/core/ajax/update.php') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$response = new TemplateResponse('', 'update.user', [], TemplateResponse::RENDER_AS_GUEST, 503);
|
||||
$response->setHeaders('X-Nextcloud-Maintenance-Mode', '1');
|
||||
$response->setHeaders('Retry-After', '120');
|
||||
Util::addScript('core', 'maintenance');
|
||||
Util::addScript('core', 'common');
|
||||
Util::addStyle('core', 'guest');
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function boot(): self {
|
||||
$this->handleAuthHeaders();
|
||||
parent::boot();
|
||||
|
||||
$this->eventLogger->start('check_server', 'Run a few configuration checks');
|
||||
$errors = OC_Util::checkServer($this->systemConfig);
|
||||
if (count($errors) > 0) {
|
||||
if (!$this->isCli()) {
|
||||
http_response_code(503);
|
||||
Util::addStyle('guest');
|
||||
try {
|
||||
$this->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 {
|
||||
$this->server->get(IAppConfig::class)->setValueArray('core', 'cronErrors', $staticErrors);
|
||||
} catch (\Exception $e) {
|
||||
echo('Writing to database failed');
|
||||
}
|
||||
exit(1);
|
||||
} elseif ($this->isCli() && $this->systemConfig->getValue('installed', false)) {
|
||||
$this->server->get(IAppConfig::class)->deleteKey('core', 'cronErrors');
|
||||
}
|
||||
$this->eventLogger->end('check_server');
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setupSession(IRequest $request, IEventLogger $eventLogger): void {
|
||||
$eventLogger->start('init_session', 'Initialize session');
|
||||
|
||||
$systemConfig = $this->server->get(SystemConfig::class);
|
||||
$appManager = $this->server->get(\OCP\App\IAppManager::class);
|
||||
if ($systemConfig->getValue('installed', false)) {
|
||||
$appManager->loadApps(['session']);
|
||||
}
|
||||
$this->initSession($request);
|
||||
$eventLogger->end('init_session');
|
||||
$this->checkInstalled($systemConfig);
|
||||
|
||||
$this->addSecurityHeaders();
|
||||
$this->performSameSiteCookieProtection($request, $this->server->get(IConfig::class));
|
||||
}
|
||||
|
||||
public function initSession(IRequest $request): void {
|
||||
// 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 = $this->webRoot ? : '/';
|
||||
ini_set('session.cookie_path', $cookie_path);
|
||||
|
||||
// set the cookie domain to the Nextcloud domain
|
||||
$cookie_domain = $this->getSystemConfig()->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(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);
|
||||
$this->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, $this->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);
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Craft the response output based on the given Response object.
|
||||
*/
|
||||
public function deliverResponse(IRequest $request, Response $response, IOutput $io): void {
|
||||
/** @var Http $protocol */
|
||||
$protocol = $this->getContainer()->get(Http::class);
|
||||
$protocol->getStatusHeader($response->getStatus());
|
||||
|
||||
// Headers
|
||||
foreach ($response->getHeaders() as $name => $value) {
|
||||
$io->setHeader($name . ': ' . $value);
|
||||
}
|
||||
|
||||
// Output
|
||||
//FIXME middleware are skipped
|
||||
$io->setOutput($response->render());
|
||||
|
||||
// Cookies
|
||||
foreach ($response->getCookies() as $name => $value) {
|
||||
$expireDate = null;
|
||||
if ($value['expireDate'] instanceof \DateTime) {
|
||||
$expireDate = $value['expireDate']->getTimestamp();
|
||||
}
|
||||
$sameSite = $value['sameSite'] ?? 'Lax';
|
||||
|
||||
$io->setCookie(
|
||||
$name,
|
||||
$value['value'],
|
||||
$expireDate,
|
||||
$this->getWebRoot(),
|
||||
null,
|
||||
$request->getServerProtocol() === 'https',
|
||||
true,
|
||||
$sameSite
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check login: apache auth, auth token, basic auth
|
||||
*/
|
||||
public function handleLogin(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 ($this->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 function tryAppAPILogin(IRequest $request): bool {
|
||||
if (!$request->getHeader('AUTHORIZATION-APP-API')) {
|
||||
return false;
|
||||
}
|
||||
$appManager = Server::get(IAppManager::class);
|
||||
if (!$appManager->isEnabledForAnyone('app_api')) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$appAPIService = Server::get(AppAPIService::class);
|
||||
return $appAPIService->validateExAppRequestToNC($request);
|
||||
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function checkInstalled(\OC\SystemConfig $systemConfig): void {
|
||||
// Redirect to installer if not installed
|
||||
if (!$systemConfig->getValue('installed', false) && $this->subUri !== '/index.php' && $this->subUri !== '/status.php') {
|
||||
if ($this->isCli()) {
|
||||
throw new Exception('Not installed');
|
||||
} else {
|
||||
$url = $this->webRoot . '/index.php';
|
||||
header('Location: ' . $url);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the same site cookies
|
||||
*/
|
||||
private 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 function performSameSiteCookieProtection(IRequest $request, IConfig $config): void {
|
||||
// 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 these 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-' . \OC::$server->getContentSecurityPolicyNonceManager()->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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\IRequest;
|
||||
|
||||
interface IHttpKernel {
|
||||
/**
|
||||
* Handle the request
|
||||
*/
|
||||
public function handle(IRequest $request, bool $catch = true): Response;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\IRequest;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
interface IKernel {
|
||||
public function boot(): void;
|
||||
|
||||
public function getServerRoot(): string;
|
||||
|
||||
public function isCli(): bool;
|
||||
|
||||
public function getContainer(): ContainerInterface;
|
||||
}
|
||||
@@ -0,0 +1,671 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use OC\AllConfig;
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OC\AppFramework\Http\RequestId;
|
||||
use OC\AppFramework\Utility\SimpleContainer;
|
||||
use OC\Config;
|
||||
use OC\Core\Listener\BeforeMessageLoggedEventListener;
|
||||
use OC\Log\ErrorHandler;
|
||||
use OC\Profiler\BuiltInProfiler;
|
||||
use OC\Security\SecureRandom;
|
||||
use OC\Server;
|
||||
use OC\Share20\GroupDeletedListener;
|
||||
use OC\Share20\UserDeletedListener;
|
||||
use OC\Share20\UserRemovedListener;
|
||||
use OC\SystemConfig;
|
||||
use OC_User;
|
||||
use OC_Util;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Console\ReservedOptions;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\Group\Events\UserRemovedEvent;
|
||||
use OCP\HintException;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Template\ITemplateManager;
|
||||
use OCP\User\Events\UserChangedEvent;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
use OCP\Util;
|
||||
use Override;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use function OCP\Log\logger;
|
||||
|
||||
require_once __DIR__ . '/../../base.php';
|
||||
|
||||
abstract class Kernel {
|
||||
protected ClassLoader $composerAutoloader;
|
||||
protected Config $config;
|
||||
protected SystemConfig $systemConfig;
|
||||
protected IEventLogger $eventLogger;
|
||||
protected string $serverRoot;
|
||||
protected string $webRoot;
|
||||
protected string $configDir;
|
||||
protected string $subUri;
|
||||
protected array $appsRoots = [];
|
||||
public Server $server;
|
||||
static Kernel $kernel;
|
||||
|
||||
public function __construct(
|
||||
) {
|
||||
$this->serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -18));
|
||||
\OC::$SERVERROOT = $this->serverRoot;
|
||||
if (isset(self::$kernel)) {
|
||||
throw new RuntimeException("Kernel is already initialized");
|
||||
}
|
||||
self::$kernel = $this;
|
||||
}
|
||||
|
||||
public static function getInstance(): self {
|
||||
return self::$kernel;
|
||||
}
|
||||
|
||||
public function getServer(): Server {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
public function boot(): self {
|
||||
$this->setRequiredIniValues();
|
||||
|
||||
$this->setupPhpDefault();
|
||||
|
||||
[$loaderStart, $loaderEnd] = $this->setupAutoloader();
|
||||
|
||||
$this->setupServerContainer();
|
||||
|
||||
$this->eventLogger = $this->server->get(IEventLogger::class);
|
||||
$this->eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
|
||||
$this->eventLogger->start('boot', 'Initialize');
|
||||
|
||||
$this->setupLogging();
|
||||
|
||||
// initialize intl fallback if necessary
|
||||
OC_Util::isSetLocaleWorking();
|
||||
|
||||
$this->setupErrorHandler();
|
||||
|
||||
/** @var Coordinator $bootstrapCoordinator */
|
||||
$bootstrapCoordinator = $this->server->get(Coordinator::class);
|
||||
$bootstrapCoordinator->runInitialRegistration();
|
||||
|
||||
$this->setupSession($this->server->get(IRequest::class), $this->eventLogger);
|
||||
|
||||
if (!$this->checkConfig()) {
|
||||
// TODO
|
||||
}
|
||||
$this->checkInstalled($this->systemConfig);
|
||||
|
||||
// User and Groups
|
||||
if (!$this->systemConfig->getValue('installed', false)) {
|
||||
$this->server->get(ISession::class)->set('user_id', '');
|
||||
}
|
||||
|
||||
$this->eventLogger->start('setup_backends', 'Setup group and user backends');
|
||||
$this->server->get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
|
||||
$this->server->get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
|
||||
|
||||
// Subscribe to the hook
|
||||
Util::connectHook(
|
||||
'\OCA\Files_Sharing\API\Server2Server',
|
||||
'preLoginNameUsedAsUserName',
|
||||
'\OC\User\Database',
|
||||
'preLoginNameUsedAsUserName'
|
||||
);
|
||||
|
||||
//setup extra user backends
|
||||
if (!Util::needUpgrade()) {
|
||||
OC_User::setupBackends();
|
||||
} else {
|
||||
// Run upgrades in incognito mode
|
||||
OC_User::setIncognitoMode(true);
|
||||
}
|
||||
$this->eventLogger->end('setup_backends');
|
||||
|
||||
self::registerCleanupHooks($this->systemConfig);
|
||||
self::registerShareHooks($this->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 set up
|
||||
if ($this->systemConfig->getValue('installed', false)) {
|
||||
$appManager = $this->server->get(IAppManager::class);
|
||||
$appManager->loadApp('settings');
|
||||
}
|
||||
|
||||
//make sure temporary files are cleaned up
|
||||
$tmpManager = $this->server->get(\OCP\ITempManager::class);
|
||||
register_shutdown_function([$tmpManager, 'clean']);
|
||||
$lockProvider = $this->server->get(\OCP\Lock\ILockingProvider::class);
|
||||
register_shutdown_function([$lockProvider, 'releaseAll']);
|
||||
|
||||
// Check whether the sample configuration has been copied
|
||||
if ($this->systemConfig->getValue('copied_sample_config', false)) {
|
||||
$l = $this->server->get(\OCP\L10N\IFactory::class)->get('lib');
|
||||
$this->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 $this;
|
||||
}
|
||||
|
||||
$request = $this->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 (!$this->isCli()
|
||||
&& !$this->server->get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
|
||||
&& $this->systemConfig->getValue('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);
|
||||
$this->server->get(LoggerInterface::class)->info(
|
||||
'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
|
||||
[
|
||||
'app' => 'core',
|
||||
'remoteAddress' => $request->getRemoteAddress(),
|
||||
'host' => $host,
|
||||
]
|
||||
);
|
||||
|
||||
$tmpl = $this->server->get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
|
||||
$tmpl->assign('docUrl', $this->server->get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
|
||||
$tmpl->printPage();
|
||||
|
||||
exit();
|
||||
}
|
||||
}
|
||||
$this->eventLogger->end('boot');
|
||||
$this->eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
|
||||
$this->eventLogger->start('runtime', 'Runtime');
|
||||
$this->eventLogger->start('request', 'Full request after boot');
|
||||
register_shutdown_function(function () {
|
||||
$this->eventLogger->end('request');
|
||||
});
|
||||
|
||||
register_shutdown_function(function () {
|
||||
$memoryPeak = memory_get_peak_usage();
|
||||
$debugModeEnabled = $this->systemConfig->getValue('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 = $this->server->get(LoggerInterface::class);
|
||||
$logger->log($logLevel, $message, ['app' => 'core']);
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isCli(): bool {
|
||||
return php_sapi_name() === 'cli';
|
||||
}
|
||||
|
||||
public function getServerRoot(): string {
|
||||
return $this->serverRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to set some values to the required Nextcloud default
|
||||
*/
|
||||
private 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException when the 3rdparty directory is missing or
|
||||
* the app path list is empty or contains an invalid path
|
||||
*/
|
||||
public function initPaths(): void {
|
||||
if (defined('PHPUNIT_CONFIG_DIR')) {
|
||||
$this->configDir = $this->getServerRoot() . '/' . PHPUNIT_CONFIG_DIR . '/';
|
||||
} elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir($this->getServerRoot() . '/tests/config/')) {
|
||||
$this->configDir = $this->getServerRoot() . '/tests/config/';
|
||||
} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
|
||||
$this->configDir = rtrim($dir, '/') . '/';
|
||||
} else {
|
||||
$this->configDir = $this->getServerRoot() . '/config/';
|
||||
}
|
||||
\OC::$configDir = $this->configDir;
|
||||
$this->config = new Config($this->configDir);
|
||||
$this->systemConfig = new SystemConfig($this->config);
|
||||
|
||||
$this->subUri = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen($this->getServerRoot())));
|
||||
/**
|
||||
* FIXME: The following lines are required because we can't yet instantiate
|
||||
* $this->server->get(\OCP\IRequest::class) since \OC::$server does not yet exist.
|
||||
*/
|
||||
$params = [
|
||||
'server' => [
|
||||
'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 Request(
|
||||
$params,
|
||||
new RequestId($_SERVER['UNIQUE_ID'] ?? '', new SecureRandom()),
|
||||
new AllConfig($this->systemConfig)
|
||||
);
|
||||
$scriptName = $fakeRequest->getScriptName();
|
||||
if (substr($scriptName, -1) == '/') {
|
||||
$scriptName .= 'index.php';
|
||||
//make sure suburi follows the same rules as scriptName
|
||||
if (substr($this->subUri, -9) != 'index.php') {
|
||||
if (substr($this->subUri, -1) != '/') {
|
||||
$this->subUri = $this->subUri . '/';
|
||||
}
|
||||
$this->subUri = $this->subUri . 'index.php';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isCli()) {
|
||||
$this->webRoot = $this->config->getValue('overwritewebroot', '');
|
||||
} else {
|
||||
if (substr($scriptName, 0 - strlen($this->subUri)) === $this->subUri) {
|
||||
$this->webRoot = substr($scriptName, 0, 0 - strlen($this->subUri));
|
||||
|
||||
if ($this->webRoot != '' && $this->webRoot[0] !== '/') {
|
||||
$this->webRoot = '/' . $this->webRoot;
|
||||
}
|
||||
} else {
|
||||
// The scriptName is not ending with Kernel::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.
|
||||
$this->webRoot = $this->config->getValue('overwritewebroot', '');
|
||||
}
|
||||
|
||||
\OC::$WEBROOT = $this->webRoot;
|
||||
|
||||
// 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'] === $this->webRoot
|
||||
&& substr($_SERVER['REQUEST_URI'], -1) !== '/') {
|
||||
header('Location: ' . $this->webRoot . '/');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// search the apps folder
|
||||
$config_paths = $this->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'], '/');
|
||||
$this->appsRoots[] = $paths;
|
||||
}
|
||||
}
|
||||
} elseif (file_exists($this->serverRoot . '/apps')) {
|
||||
$this->appsRoots[] = ['path' => $this->serverRoot . '/apps', 'url' => '/apps', 'writable' => true];
|
||||
}
|
||||
|
||||
if ($this->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.');
|
||||
}
|
||||
\OC::$APPSROOTS = $this->appsRoots;
|
||||
$paths = [];
|
||||
foreach ($this->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 function getConfigDir(): string {
|
||||
return $this->configDir;
|
||||
}
|
||||
|
||||
public function getAppsRoots(): array {
|
||||
return $this->appsRoots;
|
||||
}
|
||||
|
||||
public function addPsr4(string $prefix, string $path, bool $prepend = false): void {
|
||||
$this->composerAutoloader->addPsr4($prefix, $path, $prepend);
|
||||
}
|
||||
|
||||
public function getWebRoot(): string {
|
||||
return $this->webRoot;
|
||||
}
|
||||
|
||||
public function getContainer(): ContainerInterface {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
protected function getSystemConfig(): SystemConfig {
|
||||
return $this->systemConfig;
|
||||
}
|
||||
|
||||
protected function getSubUri(): string {
|
||||
return $this->subUri;
|
||||
}
|
||||
|
||||
public function checkConfig(): bool {
|
||||
// Create config if it does not already exist
|
||||
$configFilePath = $this->configDir . '/config.php';
|
||||
if (!file_exists($configFilePath)) {
|
||||
@touch($configFilePath);
|
||||
}
|
||||
|
||||
// Check if config is writable
|
||||
$configFileWritable = is_writable($configFilePath);
|
||||
$configReadOnly = $this->config->getValue('config_is_read_only', false);
|
||||
|
||||
return !(!$configFileWritable && !$configReadOnly || !$configFileWritable && Util::needUpgrade());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{float, float}
|
||||
*/
|
||||
protected function setupAutoloader(): array {
|
||||
// register autoloader
|
||||
$loaderStart = microtime(true);
|
||||
|
||||
// Add default composer PSR-4 autoloader, ensure apcu to be disabled
|
||||
$this->composerAutoloader = require_once $this->getServerRoot() . '/lib/composer/autoload.php';
|
||||
$this->composerAutoloader->setApcuPrefix(null);
|
||||
|
||||
try {
|
||||
$this->initPaths();
|
||||
// setup 3rdparty autoloader
|
||||
$vendorAutoLoad = $this->getServerRoot() . '/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 (!$this->isCli()) {
|
||||
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);
|
||||
return [$loaderStart, $loaderEnd];
|
||||
}
|
||||
|
||||
protected function setupErrorHandler(): void {
|
||||
$config = $this->server->get(IConfig::class);
|
||||
if (!defined('PHPUNIT_RUN')) {
|
||||
$errorHandler = new ErrorHandler(
|
||||
$this->server->get(LoggerInterface::class),
|
||||
);
|
||||
$exceptionHandler = $errorHandler->onException(...);
|
||||
if ($config->getSystemValueBool('debug', false)) {
|
||||
set_error_handler($errorHandler->onAll(...), E_ALL);
|
||||
if ($this->isCli()) {
|
||||
$exceptionHandler = $this->server->get(ITemplateManager::class)->printExceptionErrorPage(...);
|
||||
}
|
||||
} else {
|
||||
set_error_handler($errorHandler->onError(...));
|
||||
}
|
||||
register_shutdown_function($errorHandler->onShutdown(...));
|
||||
set_exception_handler($exceptionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setupServerContainer(): void {
|
||||
// Enable lazy loading if activated
|
||||
SimpleContainer::$useLazyObjects = (bool)$this->config->getValue('enable_lazy_objects', true);
|
||||
|
||||
$this->server = new Server($this->webRoot, $this->config, $this);
|
||||
$this->server->boot();
|
||||
\OC::$server = $this->server;
|
||||
|
||||
try {
|
||||
$profiler = new BuiltInProfiler(
|
||||
$this->server->get(IConfig::class),
|
||||
$this->server->get(IRequest::class),
|
||||
);
|
||||
$profiler->start();
|
||||
} catch (\Throwable $e) {
|
||||
logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setupLogging(): void {
|
||||
if ($this->isCli() && in_array('--' . ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
|
||||
BeforeMessageLoggedEventListener::setup();
|
||||
}
|
||||
|
||||
// Override php.ini and log everything if we're troubleshooting
|
||||
if ($this->config->getValue('loglevel') === ILogger::DEBUG) {
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
private function setupPhpDefault(): 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');
|
||||
}
|
||||
|
||||
// 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 HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function setupSession(IRequest $request, IEventLogger $eventLogger): void;
|
||||
|
||||
abstract public function checkInstalled(\OC\SystemConfig $systemConfig): void;
|
||||
|
||||
/**
|
||||
* register hooks for the cleanup of cache and bruteforce protection
|
||||
*/
|
||||
public function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
|
||||
//don't try to do this before we are properly setup
|
||||
if ($systemConfig->getValue('installed', false) && !Util::needUpgrade()) {
|
||||
// NOTE: This will be replaced to use OCP
|
||||
$userSession = $this->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 = $this->server->get(IRequest::class);
|
||||
$throttler = $this->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
|
||||
$this->server->get(LoggerInterface::class)->warning('Exception when running cache gc.', [
|
||||
'app' => 'core',
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function registerEncryptionWrapperAndHooks(): void {
|
||||
/** @var \OC\Encryption\Manager */
|
||||
$manager = $this->server->get(\OCP\Encryption\IManager::class);
|
||||
$this->server->get(IEventDispatcher::class)->addListener(
|
||||
BeforeFileSystemSetupEvent::class,
|
||||
$manager->setupStorage(...),
|
||||
);
|
||||
|
||||
$enabled = $manager->isEnabled();
|
||||
if ($enabled) {
|
||||
\OC\Encryption\EncryptionEventListener::register($this->server->get(IEventDispatcher::class));
|
||||
}
|
||||
}
|
||||
|
||||
private function registerAccountHooks(): void {
|
||||
/** @var IEventDispatcher $dispatcher */
|
||||
$dispatcher = $this->server->get(IEventDispatcher::class);
|
||||
$dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
|
||||
}
|
||||
|
||||
private function registerAppRestrictionsHooks(): void {
|
||||
/** @var \OC\Group\Manager $groupManager */
|
||||
$groupManager = $this->server->get(\OCP\IGroupManager::class);
|
||||
$groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
|
||||
$appManager = $this->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 function registerResourceCollectionHooks(): void {
|
||||
\OC\Collaboration\Resources\Listener::register($this->server->get(IEventDispatcher::class));
|
||||
}
|
||||
|
||||
private function registerFileReferenceEventListener(): void {
|
||||
\OC\Collaboration\Reference\File\FileReferenceEventListener::register($this->server->get(IEventDispatcher::class));
|
||||
}
|
||||
|
||||
private function registerRenderReferenceEventListener() {
|
||||
\OC\Collaboration\Reference\RenderReferenceEventListener::register($this->server->get(IEventDispatcher::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* register hooks for sharing
|
||||
*/
|
||||
public function registerShareHooks(\OC\SystemConfig $systemConfig): void {
|
||||
if ($systemConfig->getValue('installed')) {
|
||||
|
||||
$dispatcher = $this->server->get(IEventDispatcher::class);
|
||||
$dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
|
||||
$dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
|
||||
$dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
||||
* SPDX-FileContributor: Carl Schwan
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Kernel;
|
||||
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Server;
|
||||
use OCP\ServerVersion;
|
||||
use OCP\Util;
|
||||
use Throwable;
|
||||
|
||||
class UpgradePageController {
|
||||
public function __construct(
|
||||
private readonly IAppManager $appManager,
|
||||
private readonly IDBConnection $connection,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IInitialStateService $initialState,
|
||||
private readonly IUrlGenerator $urlGenerator,
|
||||
private readonly ServerVersion $serverVersion,
|
||||
private readonly IAppConfig $appConfig,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the upgrade page
|
||||
*/
|
||||
public function printUpgradePage(\OC\SystemConfig $systemConfig): Response {
|
||||
$cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
|
||||
$disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
|
||||
$tooBig = false;
|
||||
if (!$disableWebUpdater) {
|
||||
if ($this->appManager->isEnabledForAnyone('user_ldap')) {
|
||||
$qb = $this->connection->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 && $this->appManager->isEnabledForAnyone('user_saml')) {
|
||||
$qb = $this->connection->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 = $this->userManager->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');
|
||||
|
||||
$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
|
||||
if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
|
||||
// send http status 503
|
||||
http_response_code(503);
|
||||
header('Retry-After: 120');
|
||||
|
||||
$this->initialState->provideInitialState('core', 'updaterView', 'adminCli');
|
||||
$this->initialState->provideInitialState('core', 'updateInfo', [
|
||||
'cliUpgradeLink' => $cliUpgradeLink ?: $this->urlGenerator->linkToDocs('admin-cli-upgrade'),
|
||||
'productName' => self::getProductName(),
|
||||
'version' => $serverVersion->getVersionString(),
|
||||
'tooBig' => $tooBig,
|
||||
]);
|
||||
|
||||
// render error page
|
||||
return new TemplateResponse('', 'update', [], TemplateResponse::RENDER_AS_GUEST);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
$appsToUpgrade = array_map(function (array $app): array {
|
||||
return [
|
||||
'id' => $app['id'],
|
||||
'name' => $app['name'],
|
||||
'version' => $app['version'],
|
||||
'oldVersion' => $this->appConfig->getValueString($app['id'], 'installed_version'),
|
||||
];
|
||||
}, $appManager->getAppsNeedingUpgrade($ocVersion));
|
||||
|
||||
$params = [
|
||||
'appsToUpgrade' => $appsToUpgrade,
|
||||
'incompatibleAppsList' => $incompatibleDisabledApps,
|
||||
'isAppsOnlyUpgrade' => $isAppsOnlyUpgrade,
|
||||
'oldTheme' => $oldTheme,
|
||||
'productName' => self::getProductName(),
|
||||
'version' => $serverVersion->getVersionString(),
|
||||
];
|
||||
|
||||
$this->initialState->provideInitialState('core', 'updaterView', 'admin');
|
||||
$this->initialState->provideInitialState('core', 'updateInfo', $params);
|
||||
return new TemplateResponse('', 'update', [], TemplateResponse::RENDER_AS_GUEST);
|
||||
}
|
||||
|
||||
private static function getProductName(): string {
|
||||
$productName = 'Nextcloud';
|
||||
try {
|
||||
$defaults = new \OC_Defaults();
|
||||
$productName = $defaults->getName();
|
||||
} catch (Throwable $error) {
|
||||
// ignore
|
||||
}
|
||||
return $productName;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use OCP\App\AppPathNotFoundException;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Http\Attribute\Route as RouteAttribute;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
@@ -307,10 +308,9 @@ class Router implements IRouter {
|
||||
*
|
||||
* @param string $url The url to find
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
*/
|
||||
public function match($url) {
|
||||
$parameters = $this->findMatchingRoute($url);
|
||||
public function match(IRequest $request): Response {
|
||||
$parameters = $this->findMatchingRoute($request->getPathInfo());
|
||||
|
||||
$this->eventLogger->start('route:run', 'Run route');
|
||||
if (isset($parameters['caller'])) {
|
||||
@@ -318,7 +318,7 @@ class Router implements IRouter {
|
||||
unset($parameters['caller']);
|
||||
unset($parameters['action']);
|
||||
$application = $this->getApplicationClass($caller[0]);
|
||||
\OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
|
||||
return \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
|
||||
} elseif (isset($parameters['action'])) {
|
||||
$this->logger->warning('Deprecated action route used', ['parameters' => $parameters]);
|
||||
$this->callLegacyActionRoute($parameters);
|
||||
@@ -328,6 +328,7 @@ class Router implements IRouter {
|
||||
} else {
|
||||
throw new \Exception('no action available');
|
||||
}
|
||||
//FIXME what about action and file, should we return a callback response?
|
||||
$this->eventLogger->end('route:run');
|
||||
}
|
||||
|
||||
|
||||
+12
-10
@@ -83,6 +83,7 @@ use OC\Http\Client\NegativeDnsCache;
|
||||
use OC\IntegrityCheck\Checker;
|
||||
use OC\IntegrityCheck\Helpers\EnvironmentHelper;
|
||||
use OC\IntegrityCheck\Helpers\FileAccessHelper;
|
||||
use OC\Kernel\Kernel;
|
||||
use OC\KnownUser\KnownUserService;
|
||||
use OC\LDAP\NullLDAPProviderFactory;
|
||||
use OC\Lock\DBLockingProvider;
|
||||
@@ -313,12 +314,13 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
public function __construct(
|
||||
private string $webRoot,
|
||||
Config $config,
|
||||
Kernel $kernel,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
// To find out if we are running from CLI or not
|
||||
$this->registerParameter('isCLI', \OC::$CLI);
|
||||
$this->registerParameter('serverRoot', \OC::$SERVERROOT);
|
||||
$this->registerParameter('isCLI', $kernel->isCli());
|
||||
$this->registerParameter('serverRoot', $kernel->getServerRoot());
|
||||
|
||||
$this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
|
||||
return $c;
|
||||
@@ -626,13 +628,13 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
$this->registerAlias(IUserConfig::class, UserConfig::class);
|
||||
$this->registerAlias(IAppManager::class, AppManager::class);
|
||||
|
||||
$this->registerService(IFactory::class, function (Server $c) {
|
||||
$this->registerService(IFactory::class, function (Server $c) use ($kernel) {
|
||||
return new \OC\L10N\Factory(
|
||||
$c->get(IConfig::class),
|
||||
$c->getRequest(),
|
||||
$c->get(IRequest::class),
|
||||
$c->get(IUserSession::class),
|
||||
$c->get(ICacheFactory::class),
|
||||
\OC::$SERVERROOT,
|
||||
$kernel->getServerRoot(),
|
||||
$c->get(IAppManager::class),
|
||||
);
|
||||
});
|
||||
@@ -975,7 +977,7 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
$factory = $c->get(ILDAPProviderFactory::class);
|
||||
return $factory->getLDAPProvider();
|
||||
});
|
||||
$this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
|
||||
$this->registerService(ILockingProvider::class, function (ContainerInterface $c) use ($kernel) {
|
||||
$ini = $c->get(IniGetWrapper::class);
|
||||
$config = $c->get(IConfig::class);
|
||||
$ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, (int)($ini->getNumeric('max_execution_time') ?? 0)));
|
||||
@@ -991,7 +993,7 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
$c->get(IDBConnection::class),
|
||||
new TimeFactory(),
|
||||
$ttl,
|
||||
!\OC::$CLI
|
||||
!$kernel->isCli(),
|
||||
);
|
||||
}
|
||||
return new NoopLockingProvider();
|
||||
@@ -1008,12 +1010,12 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
});
|
||||
$this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
|
||||
|
||||
$this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
|
||||
$this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) use ($kernel) {
|
||||
return new Detection(
|
||||
$c->get(IURLGenerator::class),
|
||||
$c->get(LoggerInterface::class),
|
||||
\OC::$configDir,
|
||||
\OC::$SERVERROOT . '/resources/config/'
|
||||
$kernel->getConfigDir(),
|
||||
$kernel->getServerRoot() . '/resources/config/'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace OC\Session;
|
||||
use OC\Authentication\Token\IProvider;
|
||||
use OC\Diagnostics\TLogSlowOperation;
|
||||
use OCP\Authentication\Exceptions\InvalidTokenException;
|
||||
use OCP\IConfig;
|
||||
use OCP\Server;
|
||||
use OCP\Session\Exceptions\SessionNotAvailableException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -213,7 +214,7 @@ class Internal extends Session {
|
||||
|
||||
private function startSession(bool $silence = false, bool $readAndClose = true) {
|
||||
$sessionParams = ['cookie_samesite' => 'Lax'];
|
||||
if (\OC::hasSessionRelaxedExpiry()) {
|
||||
if (Server::get(IConfig::class)->getSystemValueBool('session_relaxed_expiry', false)) {
|
||||
$sessionParams['read_and_close'] = $readAndClose;
|
||||
}
|
||||
$this->invoke('session_start', [$sessionParams], $silence);
|
||||
|
||||
@@ -12,6 +12,7 @@ use OC\App\DependencyAnalyzer;
|
||||
use OC\AppFramework\App;
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Installer;
|
||||
use OC\Kernel\Kernel;
|
||||
use OC\NeedsUpdateException;
|
||||
use OC\Repair;
|
||||
use OC\Repair\Events\RepairErrorEvent;
|
||||
@@ -113,17 +114,17 @@ class OC_App {
|
||||
|
||||
// Register on PSR-4 composer autoloader
|
||||
$appNamespace = App::buildAppNamespace($app);
|
||||
\OC::$server->registerNamespace($app, $appNamespace);
|
||||
Kernel::getInstance()->getServer()->registerNamespace($app, $appNamespace);
|
||||
|
||||
if (file_exists($path . '/composer/autoload.php')) {
|
||||
require_once $path . '/composer/autoload.php';
|
||||
} else {
|
||||
\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
|
||||
Kernel::getInstance()->addPsr4($appNamespace . '\\', $path . '/lib/', true);
|
||||
}
|
||||
|
||||
// Register Test namespace only when testing
|
||||
if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
|
||||
\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
|
||||
Kernel::getInstance()->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
use OC\Kernel\Kernel;
|
||||
use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Server;
|
||||
use OCP\ServerVersion;
|
||||
use OCP\Util;
|
||||
@@ -51,7 +54,7 @@ class OC_Defaults {
|
||||
$this->defaultTextColorPrimary = '#ffffff';
|
||||
$this->defaultProductName = 'Nextcloud';
|
||||
|
||||
$themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php';
|
||||
$themePath = Kernel::getInstance()->getServerRoot() . '/themes/' . OC_Util::getTheme() . '/defaults.php';
|
||||
if (file_exists($themePath)) {
|
||||
// prevent defaults.php from printing output
|
||||
ob_start();
|
||||
@@ -214,7 +217,7 @@ class OC_Defaults {
|
||||
return $this->theme->getSlogan($lang);
|
||||
} else {
|
||||
if ($this->defaultSlogan === null) {
|
||||
$l10n = \OC::$server->getL10N('lib', $lang);
|
||||
$l10n = \OCP\Server::get(IFactory::class)->get('lib', $lang);
|
||||
$this->defaultSlogan = $l10n->t('a safe home for all your data');
|
||||
}
|
||||
return $this->defaultSlogan;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
||||
* SPDX-FileContributor: Carl Schwan
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\AppFramework\Http;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Server;
|
||||
|
||||
class ErrorTemplateResponse extends TemplateResponse {
|
||||
public function __construct(
|
||||
string $errorMessage,
|
||||
string $hint,
|
||||
string $renderAs = self::RENDER_AS_USER,
|
||||
int $status = Http::STATUS_INTERNAL_SERVER_ERROR,
|
||||
array $headers = []
|
||||
) {
|
||||
if ($errorMessage === $hint) {
|
||||
// If the hint is the same as the message there is no need to display it twice.
|
||||
$hint = '';
|
||||
}
|
||||
$errors = [['error' => $errorMessage, 'hint' => $hint]];
|
||||
$params = ['errors' => $errors];
|
||||
|
||||
|
||||
$appManager = Server::get(AppManager::class);
|
||||
if ($appManager->isEnabledForUser('theming') && !$appManager->isAppLoaded('theming')) {
|
||||
$appManager->loadApp('theming');
|
||||
}
|
||||
|
||||
parent::__construct('', 'error', $params, $renderAs, $status, $headers);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCP;
|
||||
|
||||
use OC\Kernel\Kernel;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
@@ -33,6 +34,7 @@ final class Server {
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public static function get(string $serviceName) {
|
||||
return Kernel::getInstance()->getContainer()->get($serviceName);
|
||||
/** @psalm-suppress UndefinedClass */
|
||||
return \OC::$server->get($serviceName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user