diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 1d6d14a503f54..d471f9976a4a1 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -4415,15 +4415,4 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/public.php b/public.php index 8ae6deff20305..02c0dfdba05bc 100644 --- a/public.php +++ b/public.php @@ -2,8 +2,6 @@ declare(strict_types=1); -use OC\ServiceUnavailableException; - /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -12,24 +10,28 @@ require_once __DIR__ . '/lib/versioncheck.php'; +use OC\ServiceUnavailableException; use OCP\App\IAppManager; -use OCP\IConfig; use OCP\IRequest; use OCP\Server; use OCP\Template\ITemplateManager; use OCP\Util; use Psr\Log\LoggerInterface; +/** + * Resolve the requested public.php service to a handler. + * + * @param string $service + * @return string (empty if no matches) + */ function resolveService(string $service): string { - $services = [ + $publicServices = [ 'webdav' => 'dav/appinfo/v1/publicwebdav.php', 'dav' => 'dav/appinfo/v2/publicremote.php', ]; - if (isset($services[$service])) { - return $services[$service]; - } - - return Server::get(IConfig::class)->getAppValue('core', 'remote_' . $service); + + $file = $publicServices[$service] ?? ''; + return $file; } try { @@ -69,10 +71,10 @@ function resolveService(string $service): string { $file = ltrim($file, '/'); $parts = explode('/', $file, 2); $app = $parts[0]; + \OC::$REQUESTEDAPP = $app; // Load all required applications $appManager = Server::get(IAppManager::class); - \OC::$REQUESTEDAPP = $app; $appManager->loadApps(['authentication']); $appManager->loadApps(['extended_authentication']); $appManager->loadApps(['filesystem', 'logging']); @@ -93,11 +95,11 @@ function resolveService(string $service): string { if ($ex instanceof ServiceUnavailableException) { $status = 503; } - //show the user a detailed error page + // Show the user a detailed error page Server::get(LoggerInterface::class)->error($ex->getMessage(), ['app' => 'public', 'exception' => $ex]); Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, $status); } catch (Error $ex) { - //show the user a detailed error page + // Show the user a detailed error page Server::get(LoggerInterface::class)->error($ex->getMessage(), ['app' => 'public', 'exception' => $ex]); Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); } diff --git a/remote.php b/remote.php index 2cfd9d818c82f..b18a7d1dafbf4 100644 --- a/remote.php +++ b/remote.php @@ -1,8 +1,6 @@ getHeader('Content-Type'), 'text/xml'); - if ($isXmlContentType === 0) { - // fire up a simple server to properly process the exception - $server = new Server(); - if (!($e instanceof RemoteException)) { - // we shall not log on RemoteException - $server->addPlugin(new ExceptionLoggerPlugin('webdav', \OCP\Server::get(LoggerInterface::class))); - } - $server->on('beforeMethod:*', function () use ($e): void { - if ($e instanceof RemoteException) { - switch ($e->getCode()) { - case 503: - throw new ServiceUnavailable($e->getMessage()); - case 404: - throw new \Sabre\DAV\Exception\NotFound($e->getMessage()); - } - } - $class = get_class($e); - $msg = $e->getMessage(); - throw new ServiceUnavailable("$class: $msg"); - }); - $server->exec(); - } else { - $statusCode = 500; - if ($e instanceof ServiceUnavailableException) { - $statusCode = 503; - } - if ($e instanceof RemoteException) { - // we shall not log on RemoteException - \OCP\Server::get(ITemplateManager::class)->printErrorPage($e->getMessage(), '', $e->getCode()); - } else { - \OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'remote','exception' => $e]); - \OCP\Server::get(ITemplateManager::class)->printExceptionErrorPage($e, $statusCode); - } - } - } catch (\Exception $e) { - \OCP\Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500); - } -} /** + * Resolve the requested remote.php service to a handler. + * * @param string $service - * @return string + * @return string (empty if no matches) */ -function resolveService($service) { - $services = [ +function resolveService(string $service): string { + $remoteServices = [ 'webdav' => 'dav/appinfo/v1/webdav.php', 'dav' => 'dav/appinfo/v2/remote.php', 'caldav' => 'dav/appinfo/v1/caldav.php', @@ -86,11 +37,9 @@ function resolveService($service) { 'files' => 'dav/appinfo/v1/webdav.php', 'direct' => 'dav/appinfo/v2/direct.php', ]; - if (isset($services[$service])) { - return $services[$service]; - } - - return \OCP\Server::get(IConfig::class)->getAppValue('core', 'remote_' . $service); + + $file = $remoteServices[$service] ?? ''; + return $file; } try { @@ -101,52 +50,51 @@ function resolveService($service) { // this policy with a softer one if debug mode is enabled. header("Content-Security-Policy: default-src 'none';"); + // Check if Nextcloud is in maintenance mode if (Util::needUpgrade()) { // since the behavior of apps or remotes are unpredictable during // an upgrade, return a 503 directly throw new RemoteException('Service unavailable', 503); } - $request = \OCP\Server::get(IRequest::class); + $request = Server::get(IRequest::class); $pathInfo = $request->getPathInfo(); if ($pathInfo === false || $pathInfo === '') { throw new RemoteException('Path not found', 404); } + + // Extract the service from the path if (!$pos = strpos($pathInfo, '/', 1)) { $pos = strlen($pathInfo); } $service = substr($pathInfo, 1, $pos - 1); + // Resolve the service to a file $file = resolveService($service); - - if (is_null($file)) { + if (!$file) { throw new RemoteException('Path not found', 404); } + // Extract the app from the service file $file = ltrim($file, '/'); - $parts = explode('/', $file, 2); $app = $parts[0]; + \OC::$REQUESTEDAPP = $app; // Load all required applications - \OC::$REQUESTEDAPP = $app; - $appManager = \OCP\Server::get(IAppManager::class); + $appManager = Server::get(IAppManager::class); $appManager->loadApps(['authentication']); $appManager->loadApps(['extended_authentication']); $appManager->loadApps(['filesystem', 'logging']); - switch ($app) { - case 'core': - $file = OC::$SERVERROOT . '/' . $file; - break; - default: - if (!$appManager->isEnabledForUser($app)) { - throw new RemoteException('App not installed: ' . $app); - } - $appManager->loadApp($app); - $file = $appManager->getAppPath($app) . '/' . ($parts[1] ?? ''); - break; + // Check if the app is enabled + if (!$appManager->isEnabledForUser($app)) { + throw new RemoteException('App not installed: ' . $app, 503); // or maybe 404? } + + // Load the app + $appManager->loadApp($app); + $baseuri = OC::$WEBROOT . '/remote.php/' . $service . '/'; require_once $file; } catch (Exception $ex) { @@ -154,3 +102,65 @@ function resolveService($service) { } catch (Error $e) { handleException($e); } + +/** + * Class RemoteException + * Dummy exception class to be use locally to identify certain conditions + * Will not be logged to avoid DoS + */ +class RemoteException extends \Exception { +} + +function handleException(Exception|Error $e): void { + try { + // Assume XML requests are a DAV request + $contentType = Server::get(IRequest::class)->getHeader('Content-Type'); + if ( + str_contains($contentType, 'application/xml') + || str_contains($contentType, 'text/xml') + ) { + // Fire up a simple DAV server to properly process the exception + $server = new \Sabre\DAV\Server(); + if (!($e instanceof RemoteException)) { + // we shall not log on RemoteException + $server->addPlugin( + new ExceptionLoggerPlugin( + 'webdav', + Server::get(LoggerInterface::class) + ) + ); + } + $server->on('beforeMethod:*', function () use ($e): void { + if ($e instanceof RemoteException) { + switch ($e->getCode()) { + case 503: + throw new ServiceUnavailable($e->getMessage()); + case 404: + throw new NotFound($e->getMessage()); + } + } + $class = get_class($e); + $msg = $e->getMessage(); + throw new ServiceUnavailable("$class: $msg"); + }); + $server->start(); + } else { // Assume it was interactive + $statusCode = 500; + if ($e instanceof ServiceUnavailableException) { + $statusCode = 503; + } + if ($e instanceof RemoteException) { + // Show the user a detailed error page + Server::get(ITemplateManager::class)->printErrorPage($e->getMessage(), '', $e->getCode()); + // we shall not log on RemoteException + } else { + // Show the user a detailed error page + Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'remote','exception' => $e]); + Server::get(ITemplateManager::class)->printExceptionErrorPage($e, $statusCode); + } + } + } catch (\Exception $e) { // Something went very wrong; do the best we can + // Show the user a detailed error page + Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500); + } +}