Skip to content

Commit 7f0953d

Browse files
committed
refactor(dav): Replace baseuri manipulation with RootCollection for public shares
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent e90e3a7 commit 7f0953d

12 files changed

Lines changed: 129 additions & 39 deletions

File tree

apps/dav/appinfo/v1/publicwebdav.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
$linkCheckPlugin = new PublicLinkCheckPlugin();
6969
$filesDropPlugin = new FilesDropPlugin();
7070

71-
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
71+
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
7272
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
7373
/** @var FederatedShareProvider $shareProvider */
7474
$federatedShareProvider = Server::get(FederatedShareProvider::class);

apps/dav/appinfo/v1/webdav.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868

6969
$requestUri = Server::get(IRequest::class)->getRequestUri();
7070

71-
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function () {
71+
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function () {
7272
// use the view for the logged in user
7373
return Filesystem::getView();
7474
});

apps/dav/appinfo/v2/publicremote.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,8 @@
7575
$linkCheckPlugin = new PublicLinkCheckPlugin();
7676
$filesDropPlugin = new FilesDropPlugin();
7777

78-
// Define root url with /public.php/dav/files/TOKEN
7978
/** @var string $baseuri defined in public.php */
80-
preg_match('/(^files\/[a-z0-9-_]+)/i', substr($requestUri, strlen($baseuri)), $match);
81-
$baseuri = $baseuri . $match[0];
82-
83-
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
79+
$server = $serverFactory->createServer(true, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
8480
// GET must be allowed for e.g. showing images and allowing Zip downloads
8581
if ($server->httpRequest->getMethod() !== 'GET') {
8682
// If this is *not* a GET request we only allow access to public DAV from AJAX or when Server2Server is allowed

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
'OCA\\DAV\\Files\\RootCollection' => $baseDir . '/../lib/Files/RootCollection.php',
283283
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => $baseDir . '/../lib/Files/Sharing/FilesDropPlugin.php',
284284
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => $baseDir . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
285+
'OCA\\DAV\\Files\\Sharing\\RootCollection' => $baseDir . '/../lib/Files/Sharing/RootCollection.php',
285286
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => $baseDir . '/../lib/Listener/ActivityUpdaterListener.php',
286287
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => $baseDir . '/../lib/Listener/AddMissingIndicesListener.php',
287288
'OCA\\DAV\\Listener\\AddressbookListener' => $baseDir . '/../lib/Listener/AddressbookListener.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ class ComposerStaticInitDAV
297297
'OCA\\DAV\\Files\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/RootCollection.php',
298298
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/FilesDropPlugin.php',
299299
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
300+
'OCA\\DAV\\Files\\Sharing\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/Sharing/RootCollection.php',
300301
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/ActivityUpdaterListener.php',
301302
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => __DIR__ . '/..' . '/../lib/Listener/AddMissingIndicesListener.php',
302303
'OCA\\DAV\\Listener\\AddressbookListener' => __DIR__ . '/..' . '/../lib/Listener/AddressbookListener.php',

apps/dav/lib/Connector/Sabre/Directory.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
1414
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
1515
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
16+
use OCA\DAV\Storage\PublicShareWrapper;
1617
use OCP\App\IAppManager;
18+
use OCP\Constants;
1719
use OCP\Files\FileInfo;
1820
use OCP\Files\Folder;
1921
use OCP\Files\ForbiddenException;
@@ -172,7 +174,19 @@ public function createDirectory($name) {
172174
* @throws \Sabre\DAV\Exception\ServiceUnavailable
173175
*/
174176
public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N $l10n = null) {
175-
if (!$this->info->isReadable()) {
177+
$storage = $this->info->getStorage();
178+
$allowDirectory = false;
179+
if ($storage instanceof PublicShareWrapper) {
180+
$share = $storage->getShare();
181+
$allowDirectory =
182+
// Only allow directories for file drops
183+
($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ &&
184+
// And only allow it for directories which are a direct child of the share root
185+
$this->info->getId() === $share->getNodeId();
186+
}
187+
188+
// For file drop we need to be allowed to read the directory with the nickname
189+
if (!$allowDirectory && !$this->info->isReadable()) {
176190
// avoid detecting files through this way
177191
throw new NotFound();
178192
}
@@ -198,6 +212,11 @@ public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N
198212
if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
199213
$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
200214
} else {
215+
// In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
216+
if (!$this->info->isReadable()) {
217+
throw new NotFound();
218+
}
219+
201220
$node = new File($this->fileView, $info, $this->shareManager, $request, $l10n);
202221
}
203222
if ($this->tree) {

apps/dav/lib/Connector/Sabre/FilesPlugin.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -720,15 +720,15 @@ private function getMetadataFileAccessRight(Node $node, string $userId): int {
720720
*/
721721
public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) {
722722
// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
723-
if (!$this->server->tree->nodeExists($filePath)) {
724-
return;
725-
}
726-
$node = $this->server->tree->getNodeForPath($filePath);
727-
if ($node instanceof Node) {
728-
$fileId = $node->getFileId();
729-
if (!is_null($fileId)) {
730-
$this->server->httpResponse->setHeader('OC-FileId', $fileId);
723+
try {
724+
$node = $this->server->tree->getNodeForPath($filePath);
725+
if ($node instanceof Node) {
726+
$fileId = $node->getFileId();
727+
if (!is_null($fileId)) {
728+
$this->server->httpResponse->setHeader('OC-FileId', $fileId);
729+
}
731730
}
731+
} catch (NotFound) {
732732
}
733733
}
734734
}

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

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
namespace OCA\DAV\Connector\Sabre;
99

1010
use OC\Files\View;
11+
use OC\KnownUser\KnownUserService;
1112
use OCA\DAV\AppInfo\PluginManager;
1213
use OCA\DAV\CalDAV\DefaultCalendarValidator;
14+
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
1315
use OCA\DAV\DAV\CustomPropertiesBackend;
1416
use OCA\DAV\DAV\ViewOnlyPlugin;
1517
use OCA\DAV\Files\BrowserErrorPagePlugin;
@@ -28,12 +30,14 @@
2830
use OCP\IPreview;
2931
use OCP\IRequest;
3032
use OCP\ITagManager;
33+
use OCP\IUserManager;
3134
use OCP\IUserSession;
3235
use OCP\SabrePluginEvent;
3336
use OCP\SystemTag\ISystemTagManager;
3437
use OCP\SystemTag\ISystemTagObjectMapper;
3538
use Psr\Log\LoggerInterface;
3639
use Sabre\DAV\Auth\Plugin;
40+
use Sabre\DAV\SimpleCollection;
3741

3842
class ServerFactory {
3943

@@ -54,13 +58,22 @@ public function __construct(
5458
/**
5559
* @param callable $viewCallBack callback that should return the view for the dav endpoint
5660
*/
57-
public function createServer(string $baseUri,
61+
public function createServer(
62+
bool $isPublicShare,
63+
string $baseUri,
5864
string $requestUri,
5965
Plugin $authPlugin,
60-
callable $viewCallBack): Server {
66+
callable $viewCallBack,
67+
): Server {
6168
// Fire up server
62-
$objectTree = new ObjectTree();
63-
$server = new Server($objectTree);
69+
if ($isPublicShare) {
70+
$rootCollection = new SimpleCollection('root');
71+
$tree = new CachingTree($rootCollection);
72+
} else {
73+
$rootCollection = null;
74+
$tree = new ObjectTree();
75+
}
76+
$server = new Server($tree);
6477
// Set URL explicitly due to reverse-proxy situations
6578
$server->httpRequest->setUrl($requestUri);
6679
$server->setBaseUri($baseUri);
@@ -81,7 +94,7 @@ public function createServer(string $baseUri,
8194
$server->addPlugin(new RequestIdHeaderPlugin($this->request));
8295

8396
$server->addPlugin(new ZipFolderPlugin(
84-
$objectTree,
97+
$tree,
8598
$this->logger,
8699
$this->eventDispatcher,
87100
));
@@ -101,7 +114,7 @@ public function createServer(string $baseUri,
101114
}
102115

103116
// wait with registering these until auth is handled and the filesystem is setup
104-
$server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack): void {
117+
$server->on('beforeMethod:*', function () use ($server, $tree, $viewCallBack, $isPublicShare, $rootCollection): void {
105118
// ensure the skeleton is copied
106119
$userFolder = \OC::$server->getUserFolder();
107120

@@ -115,15 +128,39 @@ public function createServer(string $baseUri,
115128

116129
// Create Nextcloud Dir
117130
if ($rootInfo->getType() === 'dir') {
118-
$root = new Directory($view, $rootInfo, $objectTree);
131+
$root = new Directory($view, $rootInfo, $tree);
119132
} else {
120133
$root = new File($view, $rootInfo);
121134
}
122-
$objectTree->init($root, $view, $this->mountManager);
135+
136+
if ($isPublicShare) {
137+
$userPrincipalBackend = new Principal(
138+
\OCP\Server::get(IUserManager::class),
139+
\OCP\Server::get(IGroupManager::class),
140+
\OCP\Server::get(IAccountManager::class),
141+
\OCP\Server::get(\OCP\Share\IManager::class),
142+
\OCP\Server::get(IUserSession::class),
143+
\OCP\Server::get(IAppManager::class),
144+
\OCP\Server::get(ProxyMapper::class),
145+
\OCP\Server::get(KnownUserService::class),
146+
\OCP\Server::get(IConfig::class),
147+
\OC::$server->getL10NFactory(),
148+
);
149+
150+
// Mount the share collection at /public.php/dav/shares/<share token>
151+
$rootCollection->addChild(new \OCA\DAV\Files\Sharing\RootCollection(
152+
$root,
153+
$userPrincipalBackend,
154+
'principals/shares',
155+
));
156+
} else {
157+
/** @var ObjectTree $tree */
158+
$tree->init($root, $view, $this->mountManager);
159+
}
123160

124161
$server->addPlugin(
125162
new FilesPlugin(
126-
$objectTree,
163+
$tree,
127164
$this->config,
128165
$this->request,
129166
$this->previewManager,
@@ -143,16 +180,16 @@ public function createServer(string $baseUri,
143180
));
144181

145182
if ($this->userSession->isLoggedIn()) {
146-
$server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession));
183+
$server->addPlugin(new TagsPlugin($tree, $this->tagManager, $this->eventDispatcher, $this->userSession));
147184
$server->addPlugin(new SharesPlugin(
148-
$objectTree,
185+
$tree,
149186
$this->userSession,
150187
$userFolder,
151188
\OCP\Server::get(\OCP\Share\IManager::class)
152189
));
153190
$server->addPlugin(new CommentPropertiesPlugin(\OCP\Server::get(ICommentsManager::class), $this->userSession));
154191
$server->addPlugin(new FilesReportPlugin(
155-
$objectTree,
192+
$tree,
156193
$view,
157194
\OCP\Server::get(ISystemTagManager::class),
158195
\OCP\Server::get(ISystemTagObjectMapper::class),
@@ -167,7 +204,7 @@ public function createServer(string $baseUri,
167204
new \Sabre\DAV\PropertyStorage\Plugin(
168205
new CustomPropertiesBackend(
169206
$server,
170-
$objectTree,
207+
$tree,
171208
$this->databaseConnection,
172209
$this->userSession->getUser(),
173210
\OCP\Server::get(DefaultCalendarValidator::class),

apps/dav/lib/Files/Sharing/FilesDropPlugin.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
7373
if ($isFileRequest && ($nickName == null || trim($nickName) === '')) {
7474
throw new MethodNotAllowed('Nickname is required for file requests');
7575
}
76-
76+
7777
// If this is a file request we need to create a folder for the user
7878
if ($isFileRequest) {
7979
// Check if the folder already exists
@@ -83,9 +83,9 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
8383
// Put all files in the subfolder
8484
$path = $nickName . '/' . $path;
8585
}
86-
86+
8787
$newName = \OC_Helper::buildNotExistingFileNameForView('/', $path, $this->view);
88-
$url = $request->getBaseUrl() . $newName;
88+
$url = $request->getBaseUrl() . '/files/' . $this->share->getToken() . $newName;
8989
$request->setUrl($url);
9090
}
9191

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\DAV\Files\Sharing;
11+
12+
use Sabre\DAV\INode;
13+
use Sabre\DAVACL\AbstractPrincipalCollection;
14+
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
15+
16+
class RootCollection extends AbstractPrincipalCollection {
17+
public function __construct(
18+
private INode $root,
19+
BackendInterface $principalBackend,
20+
string $principalPrefix = 'principals',
21+
) {
22+
parent::__construct($principalBackend, $principalPrefix);
23+
}
24+
25+
public function getChildForPrincipal(array $principalInfo): INode {
26+
return $this->root;
27+
}
28+
29+
public function getName() {
30+
return 'files';
31+
}
32+
}

0 commit comments

Comments
 (0)