Skip to content

Commit 24c6a1f

Browse files
authored
Merge pull request #52182 from nextcloud/feat/dav/public-share-chunked-upload
2 parents 1c518a2 + 01a8d77 commit 24c6a1f

124 files changed

Lines changed: 328 additions & 177 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
use OCA\DAV\Files\Sharing\PublicLinkCheckPlugin;
1515
use OCA\DAV\Storage\PublicOwnerWrapper;
1616
use OCA\DAV\Storage\PublicShareWrapper;
17+
use OCA\DAV\Upload\ChunkingPlugin;
18+
use OCA\DAV\Upload\ChunkingV2Plugin;
1719
use OCA\FederatedFileSharing\FederatedShareProvider;
1820
use OCP\BeforeSabrePubliclyLoadedEvent;
1921
use OCP\Constants;
2022
use OCP\EventDispatcher\IEventDispatcher;
2123
use OCP\Files\IRootFolder;
2224
use OCP\Files\Mount\IMountManager;
25+
use OCP\ICacheFactory;
2326
use OCP\IConfig;
2427
use OCP\IDBConnection;
2528
use OCP\IPreview;
@@ -75,12 +78,8 @@
7578
$linkCheckPlugin = new PublicLinkCheckPlugin();
7679
$filesDropPlugin = new FilesDropPlugin();
7780

78-
// Define root url with /public.php/dav/files/TOKEN
7981
/** @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) {
82+
$server = $serverFactory->createServer(true, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
8483
// GET must be allowed for e.g. showing images and allowing Zip downloads
8584
if ($server->httpRequest->getMethod() !== 'GET') {
8685
// If this is *not* a GET request we only allow access to public DAV from AJAX or when Server2Server is allowed
@@ -142,6 +141,8 @@
142141

143142
$server->addPlugin($linkCheckPlugin);
144143
$server->addPlugin($filesDropPlugin);
144+
$server->addPlugin(new ChunkingV2Plugin(Server::get(ICacheFactory::class)));
145+
$server->addPlugin(new ChunkingPlugin());
145146

146147
// allow setup of additional plugins
147148
$event = new BeforeSabrePubliclyLoadedEvent($server);

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@
284284
'OCA\\DAV\\Files\\RootCollection' => $baseDir . '/../lib/Files/RootCollection.php',
285285
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => $baseDir . '/../lib/Files/Sharing/FilesDropPlugin.php',
286286
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => $baseDir . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
287+
'OCA\\DAV\\Files\\Sharing\\RootCollection' => $baseDir . '/../lib/Files/Sharing/RootCollection.php',
287288
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => $baseDir . '/../lib/Listener/ActivityUpdaterListener.php',
288289
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => $baseDir . '/../lib/Listener/AddMissingIndicesListener.php',
289290
'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
@@ -299,6 +299,7 @@ class ComposerStaticInitDAV
299299
'OCA\\DAV\\Files\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/RootCollection.php',
300300
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/FilesDropPlugin.php',
301301
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
302+
'OCA\\DAV\\Files\\Sharing\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/Sharing/RootCollection.php',
302303
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/ActivityUpdaterListener.php',
303304
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => __DIR__ . '/..' . '/../lib/Listener/AddMissingIndicesListener.php',
304305
'OCA\\DAV\\Listener\\AddressbookListener' => __DIR__ . '/..' . '/../lib/Listener/AddressbookListener.php',

apps/dav/lib/Capabilities.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ public function __construct(
1717
}
1818

1919
/**
20-
* @return array{dav: array{chunking: string, bulkupload?: string, absence-supported?: bool, absence-replacement?: bool}}
20+
* @return array{dav: array{chunking: string, public_shares_chunking: bool, bulkupload?: string, absence-supported?: bool, absence-replacement?: bool}}
2121
*/
2222
public function getCapabilities() {
2323
$capabilities = [
2424
'dav' => [
2525
'chunking' => '1.0',
26+
'public_shares_chunking' => true,
2627
]
2728
];
2829
if ($this->config->getSystemValueBool('bulkupload.enabled', true)) {

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/Principal.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ public function getPrincipalByPath($path) {
155155
'uri' => 'principals/system/' . $name,
156156
'{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'),
157157
];
158+
} elseif ($prefix === 'principals/shares') {
159+
return [
160+
'uri' => 'principals/shares/' . $name,
161+
'{DAV:}displayname' => $name,
162+
];
158163
}
159164
return null;
160165
}

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

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@
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;
18+
use OCA\DAV\Upload\CleanupService;
1619
use OCA\Theming\ThemingDefaults;
1720
use OCP\Accounts\IAccountManager;
1821
use OCP\App\IAppManager;
1922
use OCP\Comments\ICommentsManager;
2023
use OCP\EventDispatcher\IEventDispatcher;
2124
use OCP\Files\Folder;
2225
use OCP\Files\IFilenameValidator;
26+
use OCP\Files\IRootFolder;
2327
use OCP\Files\Mount\IMountManager;
2428
use OCP\IConfig;
2529
use OCP\IDBConnection;
@@ -28,12 +32,14 @@
2832
use OCP\IPreview;
2933
use OCP\IRequest;
3034
use OCP\ITagManager;
35+
use OCP\IUserManager;
3136
use OCP\IUserSession;
3237
use OCP\SabrePluginEvent;
3338
use OCP\SystemTag\ISystemTagManager;
3439
use OCP\SystemTag\ISystemTagObjectMapper;
3540
use Psr\Log\LoggerInterface;
3641
use Sabre\DAV\Auth\Plugin;
42+
use Sabre\DAV\SimpleCollection;
3743

3844
class ServerFactory {
3945

@@ -54,13 +60,22 @@ public function __construct(
5460
/**
5561
* @param callable $viewCallBack callback that should return the view for the dav endpoint
5662
*/
57-
public function createServer(string $baseUri,
63+
public function createServer(
64+
bool $isPublicShare,
65+
string $baseUri,
5866
string $requestUri,
5967
Plugin $authPlugin,
60-
callable $viewCallBack): Server {
68+
callable $viewCallBack,
69+
): Server {
6170
// Fire up server
62-
$objectTree = new ObjectTree();
63-
$server = new Server($objectTree);
71+
if ($isPublicShare) {
72+
$rootCollection = new SimpleCollection('root');
73+
$tree = new CachingTree($rootCollection);
74+
} else {
75+
$rootCollection = null;
76+
$tree = new ObjectTree();
77+
}
78+
$server = new Server($tree);
6479
// Set URL explicitly due to reverse-proxy situations
6580
$server->httpRequest->setUrl($requestUri);
6681
$server->setBaseUri($baseUri);
@@ -81,7 +96,7 @@ public function createServer(string $baseUri,
8196
$server->addPlugin(new RequestIdHeaderPlugin($this->request));
8297

8398
$server->addPlugin(new ZipFolderPlugin(
84-
$objectTree,
99+
$tree,
85100
$this->logger,
86101
$this->eventDispatcher,
87102
));
@@ -101,7 +116,7 @@ public function createServer(string $baseUri,
101116
}
102117

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

@@ -115,15 +130,49 @@ public function createServer(string $baseUri,
115130

116131
// Create Nextcloud Dir
117132
if ($rootInfo->getType() === 'dir') {
118-
$root = new Directory($view, $rootInfo, $objectTree);
133+
$root = new Directory($view, $rootInfo, $tree);
119134
} else {
120135
$root = new File($view, $rootInfo);
121136
}
122-
$objectTree->init($root, $view, $this->mountManager);
137+
138+
if ($isPublicShare) {
139+
$userPrincipalBackend = new Principal(
140+
\OCP\Server::get(IUserManager::class),
141+
\OCP\Server::get(IGroupManager::class),
142+
\OCP\Server::get(IAccountManager::class),
143+
\OCP\Server::get(\OCP\Share\IManager::class),
144+
\OCP\Server::get(IUserSession::class),
145+
\OCP\Server::get(IAppManager::class),
146+
\OCP\Server::get(ProxyMapper::class),
147+
\OCP\Server::get(KnownUserService::class),
148+
\OCP\Server::get(IConfig::class),
149+
\OC::$server->getL10NFactory(),
150+
);
151+
152+
// Mount the share collection at /public.php/dav/shares/<share token>
153+
$rootCollection->addChild(new \OCA\DAV\Files\Sharing\RootCollection(
154+
$root,
155+
$userPrincipalBackend,
156+
'principals/shares',
157+
));
158+
159+
// Mount the upload collection at /public.php/dav/uploads/<share token>
160+
$rootCollection->addChild(new \OCA\DAV\Upload\RootCollection(
161+
$userPrincipalBackend,
162+
'principals/shares',
163+
\OCP\Server::get(CleanupService::class),
164+
\OCP\Server::get(IRootFolder::class),
165+
\OCP\Server::get(IUserSession::class),
166+
\OCP\Server::get(\OCP\Share\IManager::class),
167+
));
168+
} else {
169+
/** @var ObjectTree $tree */
170+
$tree->init($root, $view, $this->mountManager);
171+
}
123172

124173
$server->addPlugin(
125174
new FilesPlugin(
126-
$objectTree,
175+
$tree,
127176
$this->config,
128177
$this->request,
129178
$this->previewManager,
@@ -143,16 +192,16 @@ public function createServer(string $baseUri,
143192
));
144193

145194
if ($this->userSession->isLoggedIn()) {
146-
$server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession));
195+
$server->addPlugin(new TagsPlugin($tree, $this->tagManager, $this->eventDispatcher, $this->userSession));
147196
$server->addPlugin(new SharesPlugin(
148-
$objectTree,
197+
$tree,
149198
$this->userSession,
150199
$userFolder,
151200
\OCP\Server::get(\OCP\Share\IManager::class)
152201
));
153202
$server->addPlugin(new CommentPropertiesPlugin(\OCP\Server::get(ICommentsManager::class), $this->userSession));
154203
$server->addPlugin(new FilesReportPlugin(
155-
$objectTree,
204+
$tree,
156205
$view,
157206
\OCP\Server::get(ISystemTagManager::class),
158207
\OCP\Server::get(ISystemTagObjectMapper::class),
@@ -167,7 +216,7 @@ public function createServer(string $baseUri,
167216
new \Sabre\DAV\PropertyStorage\Plugin(
168217
new CustomPropertiesBackend(
169218
$server,
170-
$objectTree,
219+
$tree,
171220
$this->databaseConnection,
172221
$this->userSession->getUser(),
173222
\OCP\Server::get(DefaultCalendarValidator::class),

0 commit comments

Comments
 (0)