2222use Sabre \DAV \Xml \Response \MultiStatus ;
2323use Sabre \DAV \Xml \Service ;
2424use Sabre \VObject \Reader ;
25+ use Sabre \Xml \ParseException ;
2526use function is_null ;
2627
2728class SyncService {
@@ -43,9 +44,10 @@ public function __construct(
4344 }
4445
4546 /**
47+ * @psalm-return list{0: ?string, 1: boolean}
4648 * @throws \Exception
4749 */
48- public function syncRemoteAddressBook (string $ url , string $ userName , string $ addressBookUrl , string $ sharedSecret , ?string $ syncToken , string $ targetBookHash , string $ targetPrincipal , array $ targetProperties ): string {
50+ public function syncRemoteAddressBook (string $ url , string $ userName , string $ addressBookUrl , string $ sharedSecret , ?string $ syncToken , string $ targetBookHash , string $ targetPrincipal , array $ targetProperties ): array {
4951 // 1. create addressbook
5052 $ book = $ this ->ensureSystemAddressBookExists ($ targetPrincipal , $ targetBookHash , $ targetProperties );
5153 $ addressBookId = $ book ['id ' ];
@@ -83,7 +85,10 @@ public function syncRemoteAddressBook(string $url, string $userName, string $add
8385 }
8486 }
8587
86- return $ response ['token ' ];
88+ return [
89+ $ response ['token ' ],
90+ $ response ['truncated ' ],
91+ ];
8792 }
8893
8994 /**
@@ -127,7 +132,7 @@ public function ensureLocalSystemAddressBookExists(): ?array {
127132
128133 private function prepareUri (string $ host , string $ path ): string {
129134 /*
130- * The trailing slash is important for merging the uris together .
135+ * The trailing slash is important for merging the uris.
131136 *
132137 * $host is stored in oc_trusted_servers.url and usually without a trailing slash.
133138 *
@@ -158,7 +163,9 @@ private function prepareUri(string $host, string $path): string {
158163 }
159164
160165 /**
166+ * @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool}
161167 * @throws ClientExceptionInterface
168+ * @throws ParseException
162169 */
163170 protected function requestSyncReport (string $ url , string $ userName , string $ addressBookUrl , string $ sharedSecret , ?string $ syncToken ): array {
164171 $ client = $ this ->clientService ->newClient ();
@@ -181,7 +188,7 @@ protected function requestSyncReport(string $url, string $userName, string $addr
181188 $ body = $ response ->getBody ();
182189 assert (is_string ($ body ));
183190
184- return $ this ->parseMultiStatus ($ body );
191+ return $ this ->parseMultiStatus ($ body, $ addressBookUrl );
185192 }
186193
187194 protected function download (string $ url , string $ userName , string $ sharedSecret , string $ resourcePath ): string {
@@ -219,22 +226,50 @@ private function buildSyncCollectionRequestBody(?string $syncToken): string {
219226 }
220227
221228 /**
222- * @param string $body
223- * @return array
224- * @throws \Sabre\Xml\ParseException
229+ * @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool}
230+ * @throws ParseException
225231 */
226- private function parseMultiStatus ($ body ) {
227- $ xml = new Service ();
228-
232+ private function parseMultiStatus (string $ body , string $ addressBookUrl ): array {
229233 /** @var MultiStatus $multiStatus */
230- $ multiStatus = $ xml ->expect ('{DAV:}multistatus ' , $ body );
234+ $ multiStatus = ( new Service ()) ->expect ('{DAV:}multistatus ' , $ body );
231235
232236 $ result = [];
237+ $ truncated = false ;
238+
233239 foreach ($ multiStatus ->getResponses () as $ response ) {
234- $ result [$ response ->getHref ()] = $ response ->getResponseProperties ();
240+ $ href = $ response ->getHref ();
241+ if ($ response ->getHttpStatus () === '507 ' && $ this ->isResponseForRequestUri ($ href , $ addressBookUrl )) {
242+ $ truncated = true ;
243+ } else {
244+ $ result [$ response ->getHref ()] = $ response ->getResponseProperties ();
245+ }
235246 }
236247
237- return ['response ' => $ result , 'token ' => $ multiStatus ->getSyncToken ()];
248+ return ['response ' => $ result , 'token ' => $ multiStatus ->getSyncToken (), 'truncated ' => $ truncated ];
249+ }
250+
251+ /**
252+ * Determines whether the provided response URI corresponds to the given request URI.
253+ */
254+ private function isResponseForRequestUri (string $ responseUri , string $ requestUri ): bool {
255+ /*
256+ * Example response uri:
257+ *
258+ * /remote.php/dav/addressbooks/system/system/system/
259+ * /cloud/remote.php/dav/addressbooks/system/system/system/ (when installed in a subdirectory)
260+ *
261+ * Example request uri:
262+ *
263+ * remote.php/dav/addressbooks/system/system/system
264+ *
265+ * References:
266+ * https://github.com/nextcloud/3rdparty/blob/e0a509739b13820f0a62ff9cad5d0fede00e76ee/sabre/dav/lib/DAV/Sync/Plugin.php#L172-L174
267+ * https://github.com/nextcloud/server/blob/b40acb34a39592070d8455eb91c5364c07928c50/apps/federation/lib/SyncFederationAddressBooks.php#L41
268+ */
269+ return str_ends_with (
270+ rtrim ($ responseUri , '/ ' ),
271+ rtrim ($ requestUri , '/ ' )
272+ );
238273 }
239274
240275 /**
0 commit comments