Browse Source

Merge pull request #47787 from nextcloud/backport/47770/stable30

[stable30] fix(dav): Always respond custom error page on exceptions
pull/47796/head
Andy Scherzinger 1 year ago
committed by GitHub
parent
commit
cac3305ba9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      apps/dav/composer/composer/autoload_classmap.php
  2. 2
      apps/dav/composer/composer/autoload_static.php
  3. 6
      apps/dav/lib/Connector/Sabre/ServerFactory.php
  4. 82
      apps/dav/lib/Files/ErrorPagePlugin.php
  5. 6
      apps/dav/lib/Server.php
  6. 2
      apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml
  7. 8
      apps/dav/tests/unit/DAV/ErrorPagePluginTest.php
  8. 18
      build/integration/dav_features/caldav.feature
  9. 15
      build/integration/dav_features/carddav.feature
  10. 47
      core/templates/xml_exception.php

2
apps/dav/composer/composer/autoload_classmap.php

@ -272,7 +272,7 @@ return array(
'OCA\\DAV\\Events\\SubscriptionUpdatedEvent' => $baseDir . '/../lib/Events/SubscriptionUpdatedEvent.php',
'OCA\\DAV\\Exception\\ServerMaintenanceMode' => $baseDir . '/../lib/Exception/ServerMaintenanceMode.php',
'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => $baseDir . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php',
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.php',
'OCA\\DAV\\Files\\ErrorPagePlugin' => $baseDir . '/../lib/Files/ErrorPagePlugin.php',
'OCA\\DAV\\Files\\FileSearchBackend' => $baseDir . '/../lib/Files/FileSearchBackend.php',
'OCA\\DAV\\Files\\FilesHome' => $baseDir . '/../lib/Files/FilesHome.php',
'OCA\\DAV\\Files\\LazySearchBackend' => $baseDir . '/../lib/Files/LazySearchBackend.php',

2
apps/dav/composer/composer/autoload_static.php

@ -287,7 +287,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Events\\SubscriptionUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/SubscriptionUpdatedEvent.php',
'OCA\\DAV\\Exception\\ServerMaintenanceMode' => __DIR__ . '/..' . '/../lib/Exception/ServerMaintenanceMode.php',
'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => __DIR__ . '/..' . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php',
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.php',
'OCA\\DAV\\Files\\ErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/ErrorPagePlugin.php',
'OCA\\DAV\\Files\\FileSearchBackend' => __DIR__ . '/..' . '/../lib/Files/FileSearchBackend.php',
'OCA\\DAV\\Files\\FilesHome' => __DIR__ . '/..' . '/../lib/Files/FilesHome.php',
'OCA\\DAV\\Files\\LazySearchBackend' => __DIR__ . '/..' . '/../lib/Files/LazySearchBackend.php',

6
apps/dav/lib/Connector/Sabre/ServerFactory.php

@ -10,7 +10,7 @@ namespace OCA\DAV\Connector\Sabre;
use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\ErrorPagePlugin;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IFilenameValidator;
@ -98,9 +98,7 @@ class ServerFactory {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin());
}
if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) {
$server->addPlugin(new BrowserErrorPagePlugin());
}
$server->addPlugin(new ErrorPagePlugin($this->request, $this->config));
// wait with registering these until auth is handled and the filesystem is setup
$server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack) {

82
apps/dav/lib/Files/BrowserErrorPagePlugin.php → apps/dav/lib/Files/ErrorPagePlugin.php

@ -7,17 +7,22 @@
*/
namespace OCA\DAV\Files;
use OC\AppFramework\Http\Request;
use OC_Template;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\IConfig;
use OCP\IRequest;
use Sabre\DAV\Exception;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
class BrowserErrorPagePlugin extends ServerPlugin {
/** @var Server */
private $server;
class ErrorPagePlugin extends ServerPlugin {
private ?Server $server = null;
public function __construct(
private IRequest $request,
private IConfig $config,
) {
}
/**
* This initializes the plugin.
@ -26,35 +31,12 @@ class BrowserErrorPagePlugin extends ServerPlugin {
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
public function initialize(Server $server) {
public function initialize(Server $server): void {
$this->server = $server;
$server->on('exception', [$this, 'logException'], 1000);
}
/**
* @param IRequest $request
* @return bool
*/
public static function isBrowserRequest(IRequest $request) {
if ($request->getMethod() !== 'GET') {
return false;
}
return $request->isUserAgent([
Request::USER_AGENT_IE,
Request::USER_AGENT_MS_EDGE,
Request::USER_AGENT_CHROME,
Request::USER_AGENT_FIREFOX,
Request::USER_AGENT_SAFARI,
]);
}
/**
* @param \Throwable $ex
*/
public function logException(\Throwable $ex): void {
if ($ex instanceof Exception) {
$httpCode = $ex->getHTTPCode();
@ -65,7 +47,7 @@ class BrowserErrorPagePlugin extends ServerPlugin {
}
$this->server->httpResponse->addHeaders($headers);
$this->server->httpResponse->setStatus($httpCode);
$body = $this->generateBody($httpCode);
$body = $this->generateBody($ex, $httpCode);
$this->server->httpResponse->setBody($body);
$csp = new ContentSecurityPolicy();
$this->server->httpResponse->addHeader('Content-Security-Policy', $csp->buildPolicy());
@ -76,18 +58,32 @@ class BrowserErrorPagePlugin extends ServerPlugin {
* @codeCoverageIgnore
* @return bool|string
*/
public function generateBody(int $httpCode) {
$request = \OC::$server->getRequest();
$templateName = 'exception';
if ($httpCode === 403 || $httpCode === 404) {
$templateName = (string)$httpCode;
public function generateBody(\Throwable $ex, int $httpCode): mixed {
if ($this->acceptHtml()) {
$templateName = 'exception';
$renderAs = 'guest';
if ($httpCode === 403 || $httpCode === 404) {
$templateName = (string)$httpCode;
}
} else {
$templateName = 'xml_exception';
$renderAs = null;
$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
}
$content = new OC_Template('core', $templateName, 'guest');
$debug = $this->config->getSystemValueBool('debug', false);
$content = new OC_Template('core', $templateName, $renderAs);
$content->assign('title', $this->server->httpResponse->getStatusText());
$content->assign('remoteAddr', $request->getRemoteAddress());
$content->assign('requestID', $request->getId());
$content->assign('remoteAddr', $this->request->getRemoteAddress());
$content->assign('requestID', $this->request->getId());
$content->assign('debugMode', $debug);
$content->assign('errorClass', get_class($ex));
$content->assign('errorMsg', $ex->getMessage());
$content->assign('errorCode', $ex->getCode());
$content->assign('file', $ex->getFile());
$content->assign('line', $ex->getLine());
$content->assign('exception', $ex);
return $content->fetchPage();
}
@ -98,4 +94,14 @@ class BrowserErrorPagePlugin extends ServerPlugin {
$this->server->sapi->sendResponse($this->server->httpResponse);
exit();
}
private function acceptHtml(): bool {
foreach (explode(',', $this->request->getHeader('Accept')) as $part) {
$subparts = explode(';', $part);
if (str_ends_with($subparts[0], '/html')) {
return true;
}
}
return false;
}
}

6
apps/dav/lib/Server.php

@ -43,7 +43,7 @@ use OCA\DAV\DAV\PublicAuth;
use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Events\SabrePluginAddEvent;
use OCA\DAV\Events\SabrePluginAuthInitEvent;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\ErrorPagePlugin;
use OCA\DAV\Files\LazySearchBackend;
use OCA\DAV\Profiler\ProfilerPlugin;
use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin;
@ -221,9 +221,7 @@ class Server {
$this->server->addPlugin(new FakeLockerPlugin());
}
if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
$this->server->addPlugin(new BrowserErrorPagePlugin());
}
$this->server->addPlugin(new ErrorPagePlugin($this->request, \OC::$server->getConfig()));
$lazySearchBackend = new LazySearchBackend();
$this->server->addPlugin(new SearchPlugin($lazySearchBackend));

2
apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml

@ -2701,7 +2701,7 @@
<callback>prepostcondition</callback>
<arg>
<name>error</name>
<value>{DAV:}valid-sync-token</value>
<value>{http://sabredav.org/ns}exception</value>
</arg>
<arg>
<name>ignoreextras</name>

8
apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php → apps/dav/tests/unit/DAV/ErrorPagePluginTest.php

@ -7,11 +7,11 @@
*/
namespace OCA\DAV\Tests\unit\DAV;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\ErrorPagePlugin;
use Sabre\DAV\Exception\NotFound;
use Sabre\HTTP\Response;
class BrowserErrorPagePluginTest extends \Test\TestCase {
class ErrorPagePluginTest extends \Test\TestCase {
/**
* @dataProvider providesExceptions
@ -19,8 +19,8 @@ class BrowserErrorPagePluginTest extends \Test\TestCase {
* @param $exception
*/
public function test($expectedCode, $exception): void {
/** @var BrowserErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */
$plugin = $this->getMockBuilder(BrowserErrorPagePlugin::class)->setMethods(['sendResponse', 'generateBody'])->getMock();
/** @var ErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */
$plugin = $this->getMockBuilder(ErrorPagePlugin::class)->disableOriginalConstructor()->setMethods(['sendResponse', 'generateBody'])->getMock();
$plugin->expects($this->once())->method('generateBody')->willReturn(':boom:');
$plugin->expects($this->once())->method('sendResponse');
/** @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject $server */

18
build/integration/dav_features/caldav.feature

@ -5,8 +5,7 @@ Feature: caldav
Given user "user0" exists
When "admin" requests calendar "user0/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Node with name 'MyCalendar' could not be found"
And The exception is "Internal Server Error"
Scenario: Accessing a not shared calendar of another user
Given user "user0" exists
@ -14,8 +13,7 @@ Feature: caldav
Given The CalDAV HTTP status code should be "201"
When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Calendar with name 'MyCalendar' could not be found"
And The exception is "Internal Server Error"
Scenario: Accessing a not shared calendar of another user via the legacy endpoint
Given user "user0" exists
@ -30,8 +28,7 @@ Feature: caldav
Given user "user0" exists
When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Node with name 'MyCalendar' could not be found"
And The exception is "Internal Server Error"
Scenario: Accessing a not existing calendar of another user via the legacy endpoint
Given user "user0" exists
@ -44,8 +41,7 @@ Feature: caldav
Given user "user0" exists
When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Node with name 'MyCalendar' could not be found"
And The exception is "Internal Server Error"
Scenario: Creating a new calendar
When "admin" creates a calendar named "MyCalendar"
@ -66,8 +62,7 @@ Feature: caldav
Given user "user0" exists
When "user0" sends a create calendar request to "admin/MyCalendar2" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Node with name 'admin' could not be found"
And The exception is "Internal Server Error"
Scenario: Create calendar request for existing calendar of another user
Given user "user0" exists
@ -75,8 +70,7 @@ Feature: caldav
Then The CalDAV HTTP status code should be "201"
When "user0" sends a create calendar request to "admin/MyCalendar2" on the endpoint "/remote.php/dav/calendars/"
Then The CalDAV HTTP status code should be "404"
And The exception is "Sabre\DAV\Exception\NotFound"
And The error message is "Node with name 'admin' could not be found"
And The exception is "Internal Server Error"
Scenario: Update a principal's schedule-default-calendar-URL
Given user "user0" exists

15
build/integration/dav_features/carddav.feature

@ -4,15 +4,13 @@ Feature: carddav
Scenario: Accessing a not existing addressbook of another user
Given user "user0" exists
When "admin" requests addressbook "user0/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
And The CardDAV exception is "Internal Server Error"
Scenario: Accessing a not shared addressbook of another user
Given user "user0" exists
Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
And The CardDAV exception is "Internal Server Error"
Scenario: Accessing a not existing addressbook of another user via legacy endpoint
Given user "user0" exists
@ -30,8 +28,7 @@ Feature: carddav
Scenario: Accessing a not existing addressbook of myself
Given user "user0" exists
When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
And The CardDAV exception is "Internal Server Error"
Scenario: Creating a new addressbook
When "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
@ -69,13 +66,11 @@ Feature: carddav
Given user "user0" exists
When "user0" sends a create addressbook request to "admin/MyAddressbook2" on the endpoint "/remote.php/dav/addressbooks/"
Then The CardDAV HTTP status code should be "404"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "File not found: admin in 'addressbooks'"
And The CardDAV exception is "Internal Server Error"
Scenario: Create addressbook request for existing addressbook of another user
Given user "user0" exists
When "admin" creates an addressbook named "MyAddressbook2" with statuscode "201"
When "user0" sends a create addressbook request to "admin/MyAddressbook2" on the endpoint "/remote.php/dav/addressbooks/"
Then The CardDAV HTTP status code should be "404"
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
And The CardDAV error message is "File not found: admin in 'addressbooks'"
And The CardDAV exception is "Internal Server Error"

47
core/templates/xml_exception.php

@ -0,0 +1,47 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2012-2015 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
function print_exception(Throwable $e, \OCP\IL10N $l): void {
p($e->getTraceAsString());
if ($e->getPrevious() !== null) {
print_unescaped('<s:previous-exception>');
print_exception($e->getPrevious(), $l);
print_unescaped('</s:previous-exception>');
}
}
?>
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
<s:exception><?php p($l->t('Internal Server Error')) ?></s:exception>
<s:message>
<?php p($l->t('The server was unable to complete your request.')) ?>
<?php p($l->t('If this happens again, please send the technical details below to the server administrator.')) ?>
<?php p($l->t('More details can be found in the server log.')) ?>
<?php if (isset($_['serverLogsDocumentation']) && $_['serverLogsDocumentation'] !== ''): ?>
<?php p($l->t('For more details see the documentation ↗.'))?>: <?php print_unescaped($_['serverLogsDocumentation']) ?>
<?php endif; ?>
</s:message>
<s:technical-details>
<s:remote-address><?php p($_['remoteAddr']) ?></s:remote-address>
<s:request-id><?php p($_['requestID']) ?></s:request-id>
<?php if (isset($_['debugMode']) && $_['debugMode'] === true): ?>
<s:type><?php p($_['errorClass']) ?></s:type>
<s:code><?php p($_['errorCode']) ?></s:code>
<s:message><?php p($_['errorMsg']) ?></s:message>
<s:file><?php p($_['file']) ?></s:file>
<s:line><?php p($_['line']) ?></s:line>
<s:stacktrace>
<?php print_exception($_['exception'], $l); ?>
</s:stacktrace>
<?php endif; ?>
</s:technical-details>
</d:error>
Loading…
Cancel
Save