mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
Merge pull request #49004 from nextcloud/bug/48678/restore-dav-error-response
Bug/48678/restore dav error response
This commit is contained in:
commit
1abe9de17d
10 changed files with 148 additions and 69 deletions
|
|
@ -276,7 +276,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\\ErrorPagePlugin' => $baseDir . '/../lib/Files/ErrorPagePlugin.php',
|
||||
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.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',
|
||||
|
|
|
|||
|
|
@ -291,7 +291,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\\ErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/ErrorPagePlugin.php',
|
||||
'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.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',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use Sabre\DAV\Exception;
|
||||
use Sabre\DAV\Version;
|
||||
|
||||
/**
|
||||
* Class \OCA\DAV\Connector\Sabre\Server
|
||||
*
|
||||
|
|
@ -26,9 +29,11 @@ class Server extends \Sabre\DAV\Server {
|
|||
$this->enablePropfindDepthInfinity = true;
|
||||
}
|
||||
|
||||
// Copied from 3rdparty/sabre/dav/lib/DAV/Server.php
|
||||
// Should be them exact same without the exception output.
|
||||
public function start(): void {
|
||||
/**
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start() {
|
||||
try {
|
||||
// If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an
|
||||
// origin, we must make sure we send back HTTP/1.0 if this was
|
||||
|
|
@ -42,10 +47,74 @@ class Server extends \Sabre\DAV\Server {
|
|||
$this->httpRequest->setBaseUrl($this->getBaseUri());
|
||||
$this->invokeMethod($this->httpRequest, $this->httpResponse);
|
||||
} catch (\Throwable $e) {
|
||||
if ($e instanceof \TypeError) {
|
||||
/*
|
||||
* The TypeError includes the file path where the error occurred,
|
||||
* potentially revealing the installation directory.
|
||||
*
|
||||
* By re-throwing the exception, we ensure that the
|
||||
* default exception handler processes it.
|
||||
*/
|
||||
throw $e;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->emit('exception', [$e]);
|
||||
} catch (\Exception $ignore) {
|
||||
}
|
||||
|
||||
$DOM = new \DOMDocument('1.0', 'utf-8');
|
||||
$DOM->formatOutput = true;
|
||||
|
||||
$error = $DOM->createElementNS('DAV:', 'd:error');
|
||||
$error->setAttribute('xmlns:s', self::NS_SABREDAV);
|
||||
$DOM->appendChild($error);
|
||||
|
||||
$h = function ($v) {
|
||||
return htmlspecialchars((string)$v, ENT_NOQUOTES, 'UTF-8');
|
||||
};
|
||||
|
||||
if (self::$exposeVersion) {
|
||||
$error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION)));
|
||||
}
|
||||
|
||||
$error->appendChild($DOM->createElement('s:exception', $h(get_class($e))));
|
||||
$error->appendChild($DOM->createElement('s:message', $h($e->getMessage())));
|
||||
if ($this->debugExceptions) {
|
||||
$error->appendChild($DOM->createElement('s:file', $h($e->getFile())));
|
||||
$error->appendChild($DOM->createElement('s:line', $h($e->getLine())));
|
||||
$error->appendChild($DOM->createElement('s:code', $h($e->getCode())));
|
||||
$error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString())));
|
||||
}
|
||||
|
||||
if ($this->debugExceptions) {
|
||||
$previous = $e;
|
||||
while ($previous = $previous->getPrevious()) {
|
||||
$xPrevious = $DOM->createElement('s:previous-exception');
|
||||
$xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous))));
|
||||
$xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage())));
|
||||
$xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile())));
|
||||
$xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine())));
|
||||
$xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode())));
|
||||
$xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString())));
|
||||
$error->appendChild($xPrevious);
|
||||
}
|
||||
}
|
||||
|
||||
if ($e instanceof Exception) {
|
||||
$httpCode = $e->getHTTPCode();
|
||||
$e->serialize($this, $error);
|
||||
$headers = $e->getHTTPHeaders($this);
|
||||
} else {
|
||||
$httpCode = 500;
|
||||
$headers = [];
|
||||
}
|
||||
$headers['Content-Type'] = 'application/xml; charset=utf-8';
|
||||
|
||||
$this->httpResponse->setStatus($httpCode);
|
||||
$this->httpResponse->setHeaders($headers);
|
||||
$this->httpResponse->setBody($DOM->saveXML());
|
||||
$this->sapi->sendResponse($this->httpResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use OCA\DAV\AppInfo\PluginManager;
|
|||
use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\DAV\Files\ErrorPagePlugin;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use OCA\Theming\ThemingDefaults;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Folder;
|
||||
|
|
@ -90,7 +90,9 @@ class ServerFactory {
|
|||
$server->addPlugin(new FakeLockerPlugin());
|
||||
}
|
||||
|
||||
$server->addPlugin(new ErrorPagePlugin($this->request, $this->config));
|
||||
if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) {
|
||||
$server->addPlugin(new BrowserErrorPagePlugin());
|
||||
}
|
||||
|
||||
// wait with registering these until auth is handled and the filesystem is setup
|
||||
$server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack): void {
|
||||
|
|
|
|||
|
|
@ -7,22 +7,17 @@
|
|||
*/
|
||||
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 ErrorPagePlugin extends ServerPlugin {
|
||||
private ?Server $server = null;
|
||||
|
||||
public function __construct(
|
||||
private IRequest $request,
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
class BrowserErrorPagePlugin extends ServerPlugin {
|
||||
/** @var Server */
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* This initializes the plugin.
|
||||
|
|
@ -31,12 +26,35 @@ class ErrorPagePlugin extends ServerPlugin {
|
|||
* addPlugin is called.
|
||||
*
|
||||
* This method should set up the required event subscriptions.
|
||||
*
|
||||
* @param Server $server
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(Server $server): void {
|
||||
public function initialize(Server $server) {
|
||||
$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();
|
||||
|
|
@ -47,7 +65,7 @@ class ErrorPagePlugin extends ServerPlugin {
|
|||
}
|
||||
$this->server->httpResponse->addHeaders($headers);
|
||||
$this->server->httpResponse->setStatus($httpCode);
|
||||
$body = $this->generateBody($ex, $httpCode);
|
||||
$body = $this->generateBody($httpCode);
|
||||
$this->server->httpResponse->setBody($body);
|
||||
$csp = new ContentSecurityPolicy();
|
||||
$this->server->httpResponse->addHeader('Content-Security-Policy', $csp->buildPolicy());
|
||||
|
|
@ -58,32 +76,18 @@ class ErrorPagePlugin extends ServerPlugin {
|
|||
* @codeCoverageIgnore
|
||||
* @return bool|string
|
||||
*/
|
||||
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');
|
||||
public function generateBody(int $httpCode) {
|
||||
$request = \OC::$server->getRequest();
|
||||
|
||||
$templateName = 'exception';
|
||||
if ($httpCode === 403 || $httpCode === 404) {
|
||||
$templateName = (string)$httpCode;
|
||||
}
|
||||
|
||||
$debug = $this->config->getSystemValueBool('debug', false);
|
||||
|
||||
$content = new OC_Template('core', $templateName, $renderAs);
|
||||
$content = new OC_Template('core', $templateName, 'guest');
|
||||
$content->assign('title', $this->server->httpResponse->getStatusText());
|
||||
$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);
|
||||
$content->assign('remoteAddr', $request->getRemoteAddress());
|
||||
$content->assign('requestID', $request->getId());
|
||||
return $content->fetchPage();
|
||||
}
|
||||
|
||||
|
|
@ -92,15 +96,6 @@ class ErrorPagePlugin extends ServerPlugin {
|
|||
*/
|
||||
public function sendResponse() {
|
||||
$this->server->sapi->sendResponse($this->server->httpResponse);
|
||||
}
|
||||
|
||||
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;
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,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\ErrorPagePlugin;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use OCA\DAV\Files\FileSearchBackend;
|
||||
use OCA\DAV\Files\LazySearchBackend;
|
||||
use OCA\DAV\Profiler\ProfilerPlugin;
|
||||
|
|
@ -244,7 +244,9 @@ class Server {
|
|||
$this->server->addPlugin(new FakeLockerPlugin());
|
||||
}
|
||||
|
||||
$this->server->addPlugin(new ErrorPagePlugin($this->request, \OC::$server->getConfig()));
|
||||
if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
|
||||
$this->server->addPlugin(new BrowserErrorPagePlugin());
|
||||
}
|
||||
|
||||
$lazySearchBackend = new LazySearchBackend();
|
||||
$this->server->addPlugin(new SearchPlugin($lazySearchBackend));
|
||||
|
|
|
|||
|
|
@ -2701,7 +2701,7 @@
|
|||
<callback>prepostcondition</callback>
|
||||
<arg>
|
||||
<name>error</name>
|
||||
<value>{http://sabredav.org/ns}exception</value>
|
||||
<value>{DAV:}valid-sync-token</value>
|
||||
</arg>
|
||||
<arg>
|
||||
<name>ignoreextras</name>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
*/
|
||||
namespace OCA\DAV\Tests\unit\DAV;
|
||||
|
||||
use OCA\DAV\Files\ErrorPagePlugin;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\HTTP\Response;
|
||||
|
||||
class ErrorPagePluginTest extends \Test\TestCase {
|
||||
class BrowserErrorPagePluginTest extends \Test\TestCase {
|
||||
|
||||
/**
|
||||
* @dataProvider providesExceptions
|
||||
|
|
@ -19,8 +19,8 @@ class ErrorPagePluginTest extends \Test\TestCase {
|
|||
* @param $exception
|
||||
*/
|
||||
public function test($expectedCode, $exception): void {
|
||||
/** @var ErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */
|
||||
$plugin = $this->getMockBuilder(ErrorPagePlugin::class)->disableOriginalConstructor()->setMethods(['sendResponse', 'generateBody'])->getMock();
|
||||
/** @var BrowserErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */
|
||||
$plugin = $this->getMockBuilder(BrowserErrorPagePlugin::class)->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 */
|
||||
|
|
@ -5,7 +5,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Node with name 'MyCalendar' could not be found"
|
||||
|
||||
Scenario: Accessing a not shared calendar of another user
|
||||
Given user "user0" exists
|
||||
|
|
@ -13,7 +14,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Calendar with name 'MyCalendar' could not be found"
|
||||
|
||||
Scenario: Accessing a not shared calendar of another user via the legacy endpoint
|
||||
Given user "user0" exists
|
||||
|
|
@ -28,7 +30,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Node with name 'MyCalendar' could not be found"
|
||||
|
||||
Scenario: Accessing a not existing calendar of another user via the legacy endpoint
|
||||
Given user "user0" exists
|
||||
|
|
@ -41,7 +44,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Node with name 'MyCalendar' could not be found"
|
||||
|
||||
Scenario: Creating a new calendar
|
||||
When "admin" creates a calendar named "MyCalendar"
|
||||
|
|
@ -62,7 +66,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Node with name 'admin' could not be found"
|
||||
|
||||
Scenario: Create calendar request for existing calendar of another user
|
||||
Given user "user0" exists
|
||||
|
|
@ -70,7 +75,8 @@ 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 "Internal Server Error"
|
||||
And The exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The error message is "Node with name 'admin' could not be found"
|
||||
|
||||
Scenario: Update a principal's schedule-default-calendar-URL
|
||||
Given user "user0" exists
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ 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 "Internal Server Error"
|
||||
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
|
||||
|
||||
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 "Internal Server Error"
|
||||
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
|
||||
|
||||
Scenario: Accessing a not existing addressbook of another user via legacy endpoint
|
||||
Given user "user0" exists
|
||||
|
|
@ -28,7 +30,8 @@ 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 "Internal Server Error"
|
||||
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
|
||||
|
||||
Scenario: Creating a new addressbook
|
||||
When "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
|
||||
|
|
@ -66,11 +69,13 @@ 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 "Internal Server Error"
|
||||
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The CardDAV error message is "File not found: admin in 'addressbooks'"
|
||||
|
||||
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 "Internal Server Error"
|
||||
And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
|
||||
And The CardDAV error message is "File not found: admin in 'addressbooks'"
|
||||
|
|
|
|||
Loading…
Reference in a new issue