diff --git a/lib/CardDAV/AddressBook.php b/lib/CardDAV/AddressBook.php
index f5744f644b..c4c9dcff55 100644
--- a/lib/CardDAV/AddressBook.php
+++ b/lib/CardDAV/AddressBook.php
@@ -102,8 +102,10 @@ public function getMultipleChildren(array $paths)
$objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
$children = [];
foreach ($objs as $obj) {
- $obj['acl'] = $this->getChildACL();
- $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+ if (is_array($obj)) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+ }
}
return $children;
diff --git a/lib/DAV/Client.php b/lib/DAV/Client.php
index 187b29ba37..ac94a4d718 100644
--- a/lib/DAV/Client.php
+++ b/lib/DAV/Client.php
@@ -4,6 +4,7 @@
namespace Sabre\DAV;
+use Sabre\DAV\Xml\Response\MultiStatus;
use Sabre\HTTP;
use Sabre\Uri;
@@ -472,6 +473,7 @@ public function getAbsoluteUrl($url)
*/
public function parseMultiStatus($body)
{
+ /** @var MultiStatus $multistatus */
$multistatus = $this->xml->expect('{DAV:}multistatus', $body);
$result = [];
diff --git a/lib/DAV/Server.php b/lib/DAV/Server.php
index 3133e54ad3..afb6ecdba6 100644
--- a/lib/DAV/Server.php
+++ b/lib/DAV/Server.php
@@ -1015,19 +1015,22 @@ public function getPropertiesForMultiplePaths(array $paths, array $propertyNames
{
$result = [
];
-
$nodes = $this->tree->getMultipleNodes($paths);
-
foreach ($nodes as $path => $node) {
- $propFind = new PropFind($path, $propertyNames);
- $r = $this->getPropertiesByNode($propFind, $node);
- if ($r) {
- $result[$path] = $propFind->getResultForMultiStatus();
+ if (null === $node) {
+ $result[$path] = [];
$result[$path]['href'] = $path;
-
- $resourceType = $this->getResourceTypeForNode($node);
- if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
- $result[$path]['href'] .= '/';
+ $result[$path]['status'] = 404;
+ } else {
+ $propFind = new PropFind($path, $propertyNames);
+ $r = $this->getPropertiesByNode($propFind, $node);
+ if ($r) {
+ $result[$path] = $propFind->getResultForMultiStatus();
+ $result[$path]['href'] = $path;
+ $resourceType = $this->getResourceTypeForNode($node);
+ if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
+ $result[$path]['href'] .= '/';
+ }
}
}
}
@@ -1667,9 +1670,11 @@ private function writeMultiStatus(Writer $w, $fileProperties, bool $strip404s)
if ($strip404s) {
unset($entry[404]);
}
+ $status = isset($entry['status']) ? $entry['status'] : null;
$response = new Xml\Element\Response(
ltrim($href, '/'),
- $entry
+ $entry,
+ $status
);
$w->write([
'name' => '{DAV:}response',
diff --git a/lib/DAV/Tree.php b/lib/DAV/Tree.php
index 65b4583ceb..8f68045af9 100644
--- a/lib/DAV/Tree.php
+++ b/lib/DAV/Tree.php
@@ -4,6 +4,7 @@
namespace Sabre\DAV;
+use Sabre\DAV\Exception\NotFound;
use Sabre\Uri;
/**
@@ -269,17 +270,35 @@ public function getMultipleNodes($paths)
$result = [];
foreach ($parents as $parent => $children) {
- $parentNode = $this->getNodeForPath($parent);
+ try {
+ $parentNode = $this->getNodeForPath($parent);
+ } catch (NotFound $ex) {
+ foreach ($children as $child) {
+ $fullPath = $parent.'/'.$child;
+ $result[$fullPath] = null;
+ }
+ continue;
+ }
if ($parentNode instanceof IMultiGet) {
foreach ($parentNode->getMultipleChildren($children) as $childNode) {
$fullPath = $parent.'/'.$childNode->getName();
$result[$fullPath] = $childNode;
$this->cache[$fullPath] = $childNode;
}
+ foreach ($children as $child) {
+ $fullPath = $parent.'/'.$child;
+ if (!isset($result[$fullPath])) {
+ $result[$fullPath] = null;
+ }
+ }
} else {
foreach ($children as $child) {
$fullPath = $parent.'/'.$child;
- $result[$fullPath] = $this->getNodeForPath($fullPath);
+ try {
+ $result[$fullPath] = $this->getNodeForPath($fullPath);
+ } catch (NotFound $ex) {
+ $result[$fullPath] = null;
+ }
}
}
}
diff --git a/lib/DAV/Xml/Element/Response.php b/lib/DAV/Xml/Element/Response.php
index 86f2d33882..d91e4e0265 100644
--- a/lib/DAV/Xml/Element/Response.php
+++ b/lib/DAV/Xml/Element/Response.php
@@ -29,7 +29,7 @@ class Response implements Element
protected $href;
/**
- * Propertylist, ordered by HTTP status code.
+ * Property list, ordered by HTTP status code.
*
* @var array
*/
@@ -124,7 +124,7 @@ public function xmlSerialize(Writer $writer): void
$writer->writeElement('{DAV:}href', $writer->contextUri.\Sabre\HTTP\encodePath($this->getHref()));
$empty = true;
- $httpStatus = $this->getHTTPStatus();
+ $httpStatus = $this->getHttpStatus();
// Add propstat elements
foreach ($this->getResponseProperties() as $status => $properties) {
diff --git a/tests/Sabre/CardDAV/MultiGetTest.php b/tests/Sabre/CardDAV/MultiGetTest.php
index 3557ddaeaa..ac2b90faf6 100644
--- a/tests/Sabre/CardDAV/MultiGetTest.php
+++ b/tests/Sabre/CardDAV/MultiGetTest.php
@@ -96,4 +96,59 @@ public function testMultiGetVCard4()
],
], $result);
}
+
+ public function testMultiGet404()
+ {
+ $request = HTTP\Sapi::createFromServerArray([
+ 'REQUEST_METHOD' => 'REPORT',
+ 'REQUEST_URI' => '/addressbooks/user1/book1',
+ ]);
+
+ $request->setBody(
+ '
+
+
+
+
+
+ /addressbooks/user1/unknown/card1
+ /addressbooks/user1/book1/card1
+ /addressbooks/user1/book1/unknown-card
+'
+ );
+
+ $response = new HTTP\ResponseMock();
+
+ $this->server->httpRequest = $request;
+ $this->server->httpResponse = $response;
+
+ $this->server->exec();
+
+ $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:'.$response->body);
+
+ $this->assertXmlStringEqualsXmlString('
+
+
+ /addressbooks/user1/unknown/card1
+ HTTP/1.1 404 Not Found
+
+
+ /addressbooks/user1/book1/card1
+
+
+ "ffe3b42186ba156c84fc1581c273f01c"
+ BEGIN:VCARD
+VERSION:3.0
+UID:12345
+END:VCARD
+
+ HTTP/1.1 200 OK
+
+
+
+ /addressbooks/user1/book1/unknown-card
+ HTTP/1.1 404 Not Found
+
+', $response->body);
+ }
}
diff --git a/tests/Sabre/DAV/Sync/PluginTest.php b/tests/Sabre/DAV/Sync/PluginTest.php
index bfe0d157a6..02687d42e4 100644
--- a/tests/Sabre/DAV/Sync/PluginTest.php
+++ b/tests/Sabre/DAV/Sync/PluginTest.php
@@ -92,6 +92,7 @@ public function testSyncInitialSyncCollection()
self::assertEquals(207, $response->status, 'Full response body:'.$response->getBodyAsString());
+ /** @var DAV\Xml\Response\MultiStatus $multiStatus */
$multiStatus = $this->server->xml->parse($response->getBodyAsString());
// Checking the sync-token