diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index cefdfbae343a8..504de16303d74 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -26,6 +26,7 @@
OCA\Files\BackgroundJob\ScanFiles
OCA\Files\BackgroundJob\DeleteOrphanedItems
OCA\Files\BackgroundJob\CleanupFileLocks
+ OCA\Files\BackgroundJob\CleanupDownloadTokens
OCA\Files\BackgroundJob\CleanupDirectEditingTokens
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index dc0ecadf35526..7103ee1f7ec0b 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -101,10 +101,15 @@
'url' => '/ajax/getstoragestats.php',
'verb' => 'GET',
],
+ [
+ 'name' => 'ajax#registerDownload',
+ 'url' => '/registerDownload',
+ 'verb' => 'POST',
+ ],
[
'name' => 'ajax#download',
'url' => '/ajax/download.php',
- 'verb' => 'POST',
+ 'verb' => 'GET',
],
[
'name' => 'API#toggleShowFolder',
diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php
index 92f29bfe410ad..25760d29b20b9 100644
--- a/apps/files/lib/AppInfo/Application.php
+++ b/apps/files/lib/AppInfo/Application.php
@@ -68,6 +68,7 @@
class Application extends App implements IBootstrap {
public const APP_ID = 'files';
+ public const DL_TOKEN_PREFIX = 'dlToken_';
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
diff --git a/apps/files/lib/BackgroundJob/CleanupDownloadTokens.php b/apps/files/lib/BackgroundJob/CleanupDownloadTokens.php
new file mode 100644
index 0000000000000..900cd46dcf08a
--- /dev/null
+++ b/apps/files/lib/BackgroundJob/CleanupDownloadTokens.php
@@ -0,0 +1,60 @@
+
+ *
+ * @author Arthur Schiwon
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\files\lib\BackgroundJob;
+
+use OC\BackgroundJob\TimedJob;
+use OCA\Files\AppInfo\Application;
+use OCP\IConfig;
+
+class CleanupDownloadTokens extends TimedJob {
+ private const INTERVAL_MINUTES = 24 * 60;
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(IConfig $config) {
+ $this->interval = self::INTERVAL_MINUTES;
+ $this->config = $config;
+ }
+
+ protected function run($argument) {
+ $appKeys = $this->config->getAppKeys(Application::APP_ID);
+ foreach ($appKeys as $key) {
+ if (strpos($key, Application::DL_TOKEN_PREFIX) !== 0) {
+ continue;
+ }
+ $dataStr = $this->config->getAppValue(Application::APP_ID, $key, '');
+ if ($dataStr === '') {
+ $this->config->deleteAppValue(Application::APP_ID, $key);
+ continue;
+ }
+ $data = \json_decode($dataStr, true);
+ if (!isset($data['lastActivity']) || (time() - $data['lastActivity']) > 24 * 60 * 2) {
+ // deletes tokens that have not seen activity for 2 days
+ // the period is chosen to allow continue of downloads with network interruptions in minde
+ $this->config->deleteAppValue(Application::APP_ID, $key);
+ }
+ }
+ }
+}
diff --git a/apps/files/lib/Controller/AjaxController.php b/apps/files/lib/Controller/AjaxController.php
index ef939df15ac46..8e0db6e4ddadb 100644
--- a/apps/files/lib/Controller/AjaxController.php
+++ b/apps/files/lib/Controller/AjaxController.php
@@ -27,21 +27,40 @@
namespace OCA\Files\Controller;
use OC_Files;
+use OCA\Files\AppInfo\Application;
use OCA\Files\Helper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\NotFoundResponse;
use OCP\Files\NotFoundException;
+use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
+use OCP\Security\ISecureRandom;
+use function json_decode;
+use function json_encode;
class AjaxController extends Controller {
/** @var ISession */
private $session;
+ /** @var IConfig */
+ private $config;
- public function __construct(string $appName, IRequest $request, ISession $session) {
+ /** @var ISecureRandom */
+ private $secureRandom;
+
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ ISession $session,
+ IConfig $config,
+ ISecureRandom $secureRandom
+ ) {
parent::__construct($appName, $request);
$this->session = $session;
$this->request = $request;
+ $this->config = $config;
+ $this->secureRandom = $secureRandom;
}
/**
@@ -67,19 +86,55 @@ public function getStorageStats(string $dir = '/'): JSONResponse {
/**
* @NoAdminRequired
*/
- public function download($files, string $dir = '', string $downloadStartSecret = '') {
+ public function registerDownload($files, string $dir = '', string $downloadStartSecret = '') {
if (is_string($files)) {
$files = [$files];
} elseif (!is_array($files)) {
throw new \InvalidArgumentException('Invalid argument for files');
}
+ $attempts = 0;
+ do {
+ if ($attempts === 10) {
+ throw new \RuntimeException('Failed to create unique download token');
+ }
+ $token = $this->secureRandom->generate(15);
+ $attempts++;
+ } while ($this->config->getAppValue(Application::APP_ID, Application::DL_TOKEN_PREFIX . $token, '') !== '');
+
+ $this->config->setAppValue(
+ Application::APP_ID,
+ Application::DL_TOKEN_PREFIX . $token,
+ json_encode([
+ 'files' => $files,
+ 'dir' => $dir,
+ 'downloadStartSecret' => $downloadStartSecret,
+ 'lastActivity' => time()
+ ])
+ );
+
+ return new JSONResponse(['token' => $token]);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ */
+ public function download(string $token) {
+ $dataStr = $this->config->getAppValue(Application::APP_ID, Application::DL_TOKEN_PREFIX . $token, '');
+ if ($dataStr === '') {
+ return new NotFoundResponse();
+ }
$this->session->close();
- if (strlen($downloadStartSecret) <= 32
- && (preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1)
+ $data = json_decode($dataStr, true);
+ $data['lastActivity'] = time();
+ $this->config->setAppValue(Application::APP_ID, Application::DL_TOKEN_PREFIX . $token, json_encode($data));
+
+ if (strlen($data['downloadStartSecret']) <= 32
+ && (preg_match('!^[a-zA-Z0-9]+$!', $data['downloadStartSecret']) === 1)
) {
- setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
+ setcookie('ocDownloadStarted', $data['downloadStartSecret'], time() + 20, '/');
}
$serverParams = [ 'head' => $this->request->getMethod() === 'HEAD' ];
@@ -87,6 +142,6 @@ public function download($files, string $dir = '', string $downloadStartSecret =
$serverParams['range'] = $this->request->getHeader('Range');
}
- OC_Files::get($dir, $files, $serverParams);
+ OC_Files::get($data['dir'], $data['files'], $serverParams);
}
}
diff --git a/apps/files/src/services/Download.js b/apps/files/src/services/Download.js
index 8451a9f8587a8..d49e2898dbb24 100644
--- a/apps/files/src/services/Download.js
+++ b/apps/files/src/services/Download.js
@@ -20,7 +20,6 @@
*
*/
-import fileDownload from 'js-file-download'
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
@@ -28,24 +27,22 @@ export default class Download {
export
const
- async get(files, dir, downloadStartSecret) {
- await axios.post(
- generateUrl('apps/files/ajax/download.php'),
+ get(files, dir, downloadStartSecret) {
+ axios.post(
+ generateUrl('apps/files/registerDownload'),
{
files,
dir,
downloadStartSecret,
- },
- {
- responseType: 'blob',
}
).then(res => {
- const fileNameMatch = res.headers['content-disposition'].match(/filename="(.+)"/)
- let fileName = ''
- if (fileNameMatch.length === 2) {
- fileName = fileNameMatch[1]
+ if (res.status === 200 && res.data.token) {
+ const dlUrl = generateUrl(
+ 'apps/files/ajax/download.php?token={token}',
+ { token: res.data.token }
+ )
+ OC.redirect(dlUrl)
}
- fileDownload(res.data, fileName)
})
}