Skip to content

Commit fdfd552

Browse files
committed
Allow app to register a download provider
This allows to provide a file content for a given dav path. Signed-off-by: Louis Chemineau <louis@chmn.me>
1 parent 175ac79 commit fdfd552

File tree

9 files changed

+297
-2
lines changed

9 files changed

+297
-2
lines changed

apps/files/appinfo/routes.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
'url' => '/api/v1/openlocaleditor/{token}',
184184
'verb' => 'POST',
185185
],
186+
[
187+
/** @see DownloadController::index() */
188+
'name' => 'Download#index',
189+
'url' => '/api/v1/download',
190+
'verb' => 'GET',
191+
],
186192
],
187193
]
188194
);

apps/files/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
3737
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
3838
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
39+
'OCA\\Files\\Controller\\DownloadController' => $baseDir . '/../lib/Controller/DownloadController.php',
3940
'OCA\\Files\\Controller\\OpenLocalEditorController' => $baseDir . '/../lib/Controller/OpenLocalEditorController.php',
4041
'OCA\\Files\\Controller\\TemplateController' => $baseDir . '/../lib/Controller/TemplateController.php',
4142
'OCA\\Files\\Controller\\TransferOwnershipController' => $baseDir . '/../lib/Controller/TransferOwnershipController.php',
@@ -54,6 +55,7 @@
5455
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
5556
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
5657
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
58+
'OCA\\Files\\Provider\\FileDownloadProvider' => $baseDir . '/../lib/Provider/FileDownloadProvider.php',
5759
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
5860
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
5961
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',

apps/files/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class ComposerStaticInitFiles
5151
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
5252
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
5353
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
54+
'OCA\\Files\\Controller\\DownloadController' => __DIR__ . '/..' . '/../lib/Controller/DownloadController.php',
5455
'OCA\\Files\\Controller\\OpenLocalEditorController' => __DIR__ . '/..' . '/../lib/Controller/OpenLocalEditorController.php',
5556
'OCA\\Files\\Controller\\TemplateController' => __DIR__ . '/..' . '/../lib/Controller/TemplateController.php',
5657
'OCA\\Files\\Controller\\TransferOwnershipController' => __DIR__ . '/..' . '/../lib/Controller/TransferOwnershipController.php',
@@ -69,6 +70,7 @@ class ComposerStaticInitFiles
6970
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
7071
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
7172
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
73+
'OCA\\Files\\Provider\\FileDownloadProvider' => __DIR__ . '/..' . '/../lib/Provider/FileDownloadProvider.php',
7274
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
7375
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
7476
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',

apps/files/lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use OCA\Files\Listener\LegacyLoadAdditionalScriptsAdapter;
4646
use OCA\Files\Listener\LoadSidebarListener;
4747
use OCA\Files\Notification\Notifier;
48+
use OCA\Files\Provider\FileDownloadProvider;
4849
use OCA\Files\Search\FilesSearchProvider;
4950
use OCA\Files\Service\TagService;
5051
use OCP\Activity\IManager as IActivityManager;
@@ -120,6 +121,8 @@ public function register(IRegistrationContext $context): void {
120121
$context->registerSearchProvider(FilesSearchProvider::class);
121122

122123
$context->registerNotifierService(Notifier::class);
124+
125+
$context->registerFileDownloadProvider(FileDownloadProvider::class);
123126
}
124127

125128
public function boot(IBootContext $context): void {
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
7+
*
8+
* @author Louis Chmn <louis@chmn.me>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Files\Controller;
28+
29+
use OC\AppFramework\Bootstrap\Coordinator;
30+
use OCP\AppFramework\Http\ZipResponse;
31+
use OCP\AppFramework\Controller;
32+
use OCP\Files\File;
33+
use OCP\Files\Folder;
34+
use OCP\Files\IFileDownloadProvider;
35+
use OCP\Files\Node;
36+
use OCP\IRequest;
37+
use Psr\Log\LoggerInterface;
38+
39+
class DownloadController extends Controller {
40+
private Coordinator $coordinator;
41+
private LoggerInterface $logger;
42+
43+
public function __construct(
44+
string $appName,
45+
IRequest $request,
46+
Coordinator $coordinator,
47+
LoggerInterface $logger
48+
) {
49+
parent::__construct($appName, $request);
50+
51+
$this->request = $request;
52+
$this->coordinator = $coordinator;
53+
$this->logger = $logger;
54+
}
55+
56+
/**
57+
* @NoCSRFRequired
58+
* @PublicPage
59+
* @UserRateThrottle(limit=5, period=100)
60+
* @AnonRateThrottle(limit=1, period=100)
61+
* @BruteForceProtection(action='download_files')
62+
*/
63+
public function index(string $files): ZipResponse {
64+
$response = new ZipResponse($this->request, 'download');
65+
66+
/** @var string[] */
67+
$files = json_decode($files);
68+
69+
if (count($files) === 0) {
70+
return $response;
71+
}
72+
73+
[$firstPrefix,] = \Sabre\Uri\split($files[0]);
74+
$commonPrefix = $firstPrefix;
75+
foreach ($files as $filePath) {
76+
$commonPrefix = $this->getCommonPrefix($filePath, $commonPrefix);
77+
}
78+
79+
foreach ($files as $filePath) {
80+
$node = null;
81+
82+
foreach ($this->getProviders() as $provider) {
83+
try {
84+
$node = $provider->getNode($filePath);
85+
if ($node !== null) {
86+
break;
87+
}
88+
} catch (\Throwable $ex) {
89+
$providerClass = $provider::class;
90+
$this->logger->warning("Error while getting file content from $providerClass", ['exception' => $ex]);
91+
}
92+
}
93+
94+
if ($node === null) {
95+
continue;
96+
}
97+
98+
$this->addNode($response, $node, substr($filePath, strlen($commonPrefix)));
99+
}
100+
101+
return $response;
102+
}
103+
104+
private function getCommonPrefix(string $str1, string $str2): string {
105+
$mbStr1 = mb_str_split($str1);
106+
$mbStr2 = mb_str_split($str2);
107+
108+
for ($i = 0; $i < count($mbStr1); $i++) {
109+
if ($mbStr1[$i] !== $mbStr2[$i]) {
110+
$i--;
111+
break;
112+
}
113+
}
114+
115+
if ($i < 0) {
116+
return '';
117+
} else {
118+
return join(array_slice($mbStr1, 0, $i));
119+
}
120+
}
121+
122+
private function addNode(ZipResponse $response, Node $node, string $path): void {
123+
if ($node instanceof File) {
124+
$response->addResource($node->fopen('r'), $path, $node->getSize());
125+
}
126+
127+
if ($node instanceof Folder) {
128+
foreach ($node->getDirectoryListing() as $subnode) {
129+
$this->addNode($response, $subnode, $path.'/'.$subnode->getName());
130+
}
131+
}
132+
}
133+
134+
/**
135+
* @return IFileDownloadProvider[]
136+
*/
137+
private function getProviders() {
138+
/** @var IFileDownloadProvider[] */
139+
$providers = [];
140+
141+
$context = $this->coordinator->getRegistrationContext();
142+
if ($context === null) {
143+
throw new \Exception("Can't get download providers");
144+
}
145+
146+
$providerRegistrations = $context->getFileDownloadProviders();
147+
148+
foreach ($providerRegistrations as $registration) {
149+
$providers[] = \OCP\Server::get($registration->getService());
150+
}
151+
152+
return $providers;
153+
}
154+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
7+
*
8+
* @author Louis Chmn <louis@chmn.me>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
namespace OCA\Files\Provider;
27+
28+
use OCP\Files\Folder;
29+
use OCP\Files\Node;
30+
use OCP\Files\IFileDownloadProvider;
31+
32+
class FileDownloadProvider implements IFileDownloadProvider {
33+
private ?Folder $userFolder;
34+
35+
public function __construct(
36+
?Folder $userFolder,
37+
) {
38+
$this->userFolder = $userFolder;
39+
}
40+
41+
public function getNode(string $davPath): ?Node {
42+
if (!str_starts_with($davPath, "files/")) {
43+
return null;
44+
}
45+
46+
if ($this->userFolder === null) {
47+
return null;
48+
}
49+
50+
/** @var ?string */
51+
$userId = explode('/', $davPath, 3)[1] ?? null;
52+
if (is_null($userId) || $userId !== $this->userFolder->getOwner()->getUID()) {
53+
return null;
54+
}
55+
56+
$filePath = substr($davPath, strlen("files/$userId"));
57+
58+
return $this->userFolder->get($filePath);
59+
}
60+
}

lib/private/AppFramework/Bootstrap/RegistrationContext.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
use OCP\Dashboard\IManager;
4848
use OCP\Dashboard\IWidget;
4949
use OCP\EventDispatcher\IEventDispatcher;
50+
use OCP\Files\IFileDownloadProvider;
5051
use OCP\Files\Template\ICustomTemplateProvider;
5152
use OCP\Http\WellKnown\IHandler;
5253
use OCP\Notification\INotifier;
@@ -58,7 +59,6 @@
5859
use Throwable;
5960

6061
class RegistrationContext {
61-
6262
/** @var ServiceRegistration<ICapability>[] */
6363
private $capabilities = [];
6464

@@ -128,6 +128,9 @@ class RegistrationContext {
128128
/** @var ParameterRegistration[] */
129129
private $sensitiveMethods = [];
130130

131+
/** @var ServiceRegistration<IFileDownloadProvider>[] */
132+
private array $fileDownloadProviders = [];
133+
131134
/** @var LoggerInterface */
132135
private $logger;
133136

@@ -326,6 +329,13 @@ public function registerSensitiveMethods(string $class, array $methods): void {
326329
$methods
327330
);
328331
}
332+
333+
public function registerFileDownloadProvider(string $class): void {
334+
$this->context->registerFileDownloadProvider(
335+
$this->appId,
336+
$class
337+
);
338+
}
329339
};
330340
}
331341

@@ -461,6 +471,10 @@ public function registerSensitiveMethods(string $appId, string $class, array $me
461471
$this->sensitiveMethods[] = new ParameterRegistration($appId, $class, $methods);
462472
}
463473

474+
public function registerFileDownloadProvider(string $appId, string $class): void {
475+
$this->fileDownloadProviders[] = new ServiceRegistration($appId, $class);
476+
}
477+
464478
/**
465479
* @param App[] $apps
466480
*/
@@ -757,4 +771,11 @@ public function getUserMigrators(): array {
757771
public function getSensitiveMethods(): array {
758772
return $this->sensitiveMethods;
759773
}
774+
775+
/**
776+
* @return ServiceRegistration<IFileDownloadProvider>[]
777+
*/
778+
public function getFileDownloadProviders(): array {
779+
return $this->fileDownloadProviders;
780+
}
760781
}

lib/public/AppFramework/Bootstrap/IRegistrationContext.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
* @see IBootstrap::register()
4848
*/
4949
interface IRegistrationContext {
50-
5150
/**
5251
* @param string $capability
5352
* @psalm-param class-string<ICapability> $capability
@@ -327,4 +326,14 @@ public function registerUserMigrator(string $migratorClass): void;
327326
* @since 25.0.0
328327
*/
329328
public function registerSensitiveMethods(string $class, array $methods): void;
329+
330+
/**
331+
* Register a backend to provide file based on a dav path.
332+
*
333+
* @param string $class
334+
* @param string[] $methods
335+
* @return void
336+
* @since 26.0.0
337+
*/
338+
public function registerFileDownloadProvider(string $class): void;
330339
}

0 commit comments

Comments
 (0)