Skip to content

Commit 8cfee4d

Browse files
authored
Merge pull request #56180 from nextcloud/backport/56035/stable31
[stable31] fix(pagination): render multistatus to XML before caching
2 parents a45e1f1 + d997b1f commit 8cfee4d

File tree

2 files changed

+396
-6
lines changed

2 files changed

+396
-6
lines changed

apps/dav/lib/Paginate/PaginatePlugin.php

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Sabre\DAV\Server;
1313
use Sabre\DAV\ServerPlugin;
14+
use Sabre\DAV\Xml\Element\Response;
1415
use Sabre\HTTP\RequestInterface;
1516
use Sabre\HTTP\ResponseInterface;
1617

@@ -54,8 +55,13 @@ public function onMultiStatus(&$fileProperties): void {
5455
) {
5556
$pageSize = (int)$request->getHeader(self::PAGINATE_COUNT_HEADER) ?: $this->pageSize;
5657
$offset = (int)$request->getHeader(self::PAGINATE_OFFSET_HEADER);
58+
5759
$copyIterator = new LimitedCopyIterator($fileProperties, $pageSize, $offset);
58-
['token' => $token, 'count' => $count] = $this->cache->store($url, $copyIterator);
60+
// wrap the iterator with another that renders XML, this way we
61+
// cache XML, but we keep the first $pageSize elements as objects
62+
// to use for the response of the first page.
63+
$rendererGenerator = $this->getXmlRendererGenerator($copyIterator);
64+
['token' => $token, 'count' => $count] = $this->cache->store($url, $rendererGenerator);
5965

6066
$fileProperties = $copyIterator->getRequestedItems();
6167
$this->server->httpResponse->addHeader(self::PAGINATE_HEADER, 'true');
@@ -65,6 +71,44 @@ public function onMultiStatus(&$fileProperties): void {
6571
}
6672
}
6773

74+
/**
75+
* Returns a generator that yields rendered XML entries for the provided
76+
* $fileProperties, as they would appear in the MultiStatus response.
77+
*/
78+
private function getXmlRendererGenerator(iterable $fileProperties): \Generator {
79+
$writer = $this->server->xml->getWriter();
80+
$prefer = $this->server->getHTTPPrefer();
81+
$minimal = $prefer['return'] === 'minimal';
82+
$writer->contextUri = $this->server->getBaseUri();
83+
84+
$writer->openMemory();
85+
$writer->startDocument();
86+
$writer->startElement('{DAV:}multistatus');
87+
88+
// throw away the beginning of the document
89+
$writer->flush();
90+
91+
foreach ($fileProperties as $entry) {
92+
$href = $entry['href'];
93+
unset($entry['href']);
94+
if ($minimal) {
95+
unset($entry[404]);
96+
}
97+
$response = new Response(
98+
ltrim($href, '/'),
99+
$entry
100+
);
101+
$writer->write([
102+
'name' => '{DAV:}response',
103+
'value' => $response,
104+
]);
105+
106+
// flushing does not remove the > for the previous element
107+
// (multistatus)
108+
yield ltrim($writer->flush(), '>');
109+
}
110+
}
111+
68112
public function onMethod(RequestInterface $request, ResponseInterface $response) {
69113
$url = $this->server->httpRequest->getUrl();
70114
if (
@@ -83,11 +127,20 @@ public function onMethod(RequestInterface $request, ResponseInterface $response)
83127
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
84128
$response->setHeader('Vary', 'Brief,Prefer');
85129

86-
$prefer = $this->server->getHTTPPrefer();
87-
$minimal = $prefer['return'] === 'minimal';
88-
89-
$data = $this->server->generateMultiStatus($items, $minimal);
90-
$response->setBody($data);
130+
// as we cached strings of XML, rebuild the multistatus response
131+
// and output the RAW entries, as stored in the cache
132+
$writer = $this->server->xml->getWriter();
133+
$writer->contextUri = $this->server->getBaseUri();
134+
$writer->openMemory();
135+
$writer->startDocument();
136+
$writer->startElement('{DAV:}multistatus');
137+
foreach ($items as $item) {
138+
$writer->writeRaw($item);
139+
}
140+
$writer->endElement();
141+
$writer->endDocument();
142+
143+
$response->setBody($writer->flush());
91144

92145
return false;
93146
}

0 commit comments

Comments
 (0)