Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4415,15 +4415,4 @@
<code><![CDATA[getRequest]]></code>
</DeprecatedMethod>
</file>
<file src="public.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
</DeprecatedMethod>
</file>
<file src="remote.php">
<DeprecatedMethod>
<code><![CDATA[exec]]></code>
<code><![CDATA[getAppValue]]></code>
</DeprecatedMethod>
</file>
</files>
</files>
26 changes: 14 additions & 12 deletions public.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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']);
Expand All @@ -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);
}
172 changes: 91 additions & 81 deletions remote.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

use OC\ServiceUnavailableException;
use OCP\IConfig;
use OCP\Util;
declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
Expand All @@ -11,72 +9,25 @@
*/
require_once __DIR__ . '/lib/versioncheck.php';

use OC\ServiceUnavailableException;
use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
use OCP\App\IAppManager;
use OCP\IRequest;
use OCP\Server;
use OCP\Template\ITemplateManager;
use OCP\Util;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\ServiceUnavailable;
use Sabre\DAV\Server;

/**
* 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 {
$request = \OCP\Server::get(IRequest::class);
// in case the request content type is text/xml - we assume it's a WebDAV request
$isXmlContentType = strpos($request->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',
Expand All @@ -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 {
Expand All @@ -101,56 +50,117 @@ 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) {
handleException($ex);
} 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);
}
}
Loading