Compare commits

...

6 Commits

Author SHA1 Message Date
blizzz a2a40c753d
prefer async/await over then
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>

Co-authored-by: John Molakvoæ <skjnldsv@users.noreply.github.com>
2021-04-06 19:13:01 +02:00
Arthur Schiwon ca81e958a5
turn download class into a function
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2021-04-06 15:04:25 +02:00
Arthur Schiwon 397cecd089
register alias for DownloadManager
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2021-04-06 15:04:24 +02:00
Arthur Schiwon bc23d4eb27
(WIP) consolidate download utilities
- move registration and cleanup code to new DownloadManager
- adjust files and files_sharing controllers accordingly
- TODO: js code of files_sharing
- TODO: tests

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2021-04-06 15:04:24 +02:00
Arthur Schiwon 5b326df36c
switch to registration via POST
- download relevant data is sent per POST, a token is received
- download is started by GET and the received token
- solves the disadvantages from XHR-only download
- job to cleanup download tokens

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2021-04-06 15:04:24 +02:00
Arthur Schiwon 45552c8c59
Download files via POST request
- also turns old download php script to Controller
- increases number of possible files for lifting length restrictions in GET data

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2021-04-06 15:04:18 +02:00
18 changed files with 509 additions and 120 deletions

View File

@ -1,78 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Frank Karlitschek <frank@karlitschek.de>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr Filiciak <piotr@filiciak.pl>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
// Check if we are a user
OCP\User::checkLoggedIn();
\OC::$server->getSession()->close();
$files = isset($_GET['files']) ? (string)$_GET['files'] : '';
$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : '';
$files_list = json_decode($files);
// in case we get only a single file
if (!is_array($files_list)) {
$files_list = [$files];
}
/**
* @psalm-taint-escape cookie
*/
function cleanCookieInput(string $value): string {
if (strlen($value) > 32) {
return '';
}
if (preg_match('!^[a-zA-Z0-9]+$!', $_GET['downloadStartSecret']) !== 1) {
return '';
}
return $value;
}
/**
* this sets a cookie to be able to recognize the start of the download
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*/
if (isset($_GET['downloadStartSecret'])) {
$value = cleanCookieInput($_GET['downloadStartSecret']);
if ($value !== '') {
setcookie('ocDownloadStarted', $value, time() + 20, '/');
}
}
$server_params = [ 'head' => \OC::$server->getRequest()->getMethod() === 'HEAD' ];
/**
* Http range requests support
*/
if (isset($_SERVER['HTTP_RANGE'])) {
$server_params['range'] = \OC::$server->getRequest()->getHeader('Range');
}
OC_Files::get($dir, $files_list, $server_params);

View File

@ -101,6 +101,16 @@ $application->registerRoutes(
'url' => '/ajax/getstoragestats.php',
'verb' => 'GET',
],
[
'name' => 'ajax#registerDownload',
'url' => '/registerDownload',
'verb' => 'POST',
],
[
'name' => 'ajax#download',
'url' => '/ajax/download.php',
'verb' => 'GET',
],
[
'name' => 'API#toggleShowFolder',
'url' => '/api/v1/toggleShowFolder/{key}',
@ -174,7 +184,5 @@ $application->registerRoutes(
/** @var $this \OC\Route\Router */
$this->create('files_ajax_download', 'apps/files/ajax/download.php')
->actionInclude('files/ajax/download.php');
$this->create('files_ajax_list', 'apps/files/ajax/list.php')
->actionInclude('files/ajax/list.php');

View File

@ -621,7 +621,7 @@
};
context.fileList.showFileBusyState(filename, true);
OCA.Files.Files.handleDownload(url, disableLoadingState);
OCA.Files.Files.handleDownload(filename, dir, disableLoadingState);
}
}
});

View File

@ -1036,11 +1036,11 @@
};
if(this.getSelectedFiles().length > 1) {
OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir, true), disableLoadingState);
OCA.Files.Files.handleDownload(files, dir, disableLoadingState);
}
else {
var first = this.getSelectedFiles()[0];
OCA.Files.Files.handleDownload(this.getDownloadUrl(first.name, dir, true), disableLoadingState);
OCA.Files.Files.handleDownload(first.name, dir, disableLoadingState);
}
event.preventDefault();
},
@ -1340,8 +1340,7 @@
}
else {
var url = this.getDownloadUrl(filename, dir, true);
OCA.Files.Files.handleDownload(url);
OCA.Files.Files.handleDownload(filename, dir, undefined);
}
}
this.triedActionOnce = true;

View File

@ -363,26 +363,24 @@
* - browser now adds this cookie for the domain
* - JS periodically checks for this cookie and then knows when the download has started to call the callback
*
* @param {string} url download URL
* @param {string} files
* @param {string} dir
* @param {Function} callback function to call once the download has started
*/
handleDownload: function(url, callback) {
handleDownload: function(files, dir, callback) {
var randomToken = Math.random().toString(36).substring(2),
checkForDownloadCookie = function() {
if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){
return false;
} else {
callback();
if (callback !== undefined) {
callback();
}
return true;
}
};
if (url.indexOf('?') >= 0) {
url += '&';
} else {
url += '?';
}
OC.redirect(url + 'downloadStartSecret=' + randomToken);
OCA.Files.download(files, dir, randomToken);
OC.Util.waitFor(checkForDownloadCookie, 500);
}
};

View File

@ -68,6 +68,7 @@ use Psr\Container\ContainerInterface;
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);

View File

@ -26,15 +26,37 @@ declare(strict_types=1);
namespace OCA\Files\Controller;
use OC_Files;
use OCA\Files\Helper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\NotFoundException;
use OCP\Files\Utils\IDownloadManager;
use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
class AjaxController extends Controller {
public function __construct(string $appName, IRequest $request) {
/** @var ISession */
private $session;
/** @var IConfig */
private $config;
/** @var IDownloadManager */
private $downloadManager;
public function __construct(
string $appName,
IRequest $request,
ISession $session,
IConfig $config,
IDownloadManager $downloadManager
) {
parent::__construct($appName, $request);
$this->session = $session;
$this->request = $request;
$this->config = $config;
$this->downloadManager = $downloadManager;
}
/**
@ -56,4 +78,46 @@ class AjaxController extends Controller {
]);
}
}
/**
* @NoAdminRequired
*/
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');
}
$token = $this->downloadManager->register([
'files' => $files,
'dir' => $dir,
'downloadStartSecret' => $downloadStartSecret,
]);
return new JSONResponse(['token' => $token]);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function download(string $token) {
$data = $this->downloadManager->retrieve($token);
$this->session->close();
if (strlen($data['downloadStartSecret']) <= 32
&& (preg_match('!^[a-zA-Z0-9]+$!', $data['downloadStartSecret']) === 1)
) {
setcookie('ocDownloadStarted', $data['downloadStartSecret'], time() + 20, '/');
}
$serverParams = [ 'head' => $this->request->getMethod() === 'HEAD' ];
if (isset($_SERVER['HTTP_RANGE'])) {
$serverParams['range'] = $this->request->getHeader('Range');
}
OC_Files::get($data['dir'], $data['files'], $serverParams);
}
}

View File

@ -191,6 +191,7 @@ class ViewController extends Controller {
\OCP\Util::addStyle('files', 'merged');
\OCP\Util::addScript('files', 'merged-index');
\OCP\Util::addScript('files', 'dist/templates');
\OCP\Util::addScript('files', 'dist/download');
// mostly for the home storage's free space
// FIXME: Make non static

View File

@ -0,0 +1,28 @@
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
import download from './services/Download'
if (!window.OCA.Files) {
window.OCA.Files = {}
}
Object.assign(window.OCA.Files, { download })

View File

@ -0,0 +1,39 @@
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@nextcloud.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
export default async function(files, dir, downloadStartSecret) {
const res = await axios.post(generateUrl('apps/files/registerDownload'), {
files,
dir,
downloadStartSecret,
})
if (res.status === 200 && res.data.token) {
const dlUrl = generateUrl('apps/files/ajax/download.php?token={token}', {
token: res.data.token,
})
OC.redirect(dlUrl)
}
}

View File

@ -26,6 +26,7 @@ const path = require('path')
module.exports = {
entry: {
download: path.join(__dirname, 'src', 'download.js'),
sidebar: path.join(__dirname, 'src', 'sidebar.js'),
templates: path.join(__dirname, 'src', 'templates.js'),
'files-app-settings': path.join(__dirname, 'src', 'files-app-settings.js'),

View File

@ -295,6 +295,20 @@ OCA.Sharing.PublicApp = {
$('#download').click(function (e) {
e.preventDefault();
OC.redirect(FileList.getDownloadUrl());
var path = dir || this.getCurrentDirectory();
if (_.isArray(filename)) {
filename = JSON.stringify(filename);
}
var params = {
path: path
};
if (filename) {
params.files = filename;
}
OCA.Files.Download.get()
});
if (hideDownload === 'true') {

View File

@ -52,17 +52,23 @@ use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Viewer\Event\LoadViewer;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\AuthPublicShareController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
use OCP\AppFramework\Http\Template\LinkMenuAction;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Template\SimpleMenuAction;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Utils\IDownloadManager;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
@ -112,6 +118,8 @@ class ShareController extends AuthPublicShareController {
/** @var Share\IShare */
protected $share;
/** @var IDownloadManager */
private $downloadManager;
/**
* @param string $appName
@ -146,7 +154,9 @@ class ShareController extends AuthPublicShareController {
IAccountManager $accountManager,
IEventDispatcher $eventDispatcher,
IL10N $l10n,
Defaults $defaults) {
Defaults $defaults,
IDownloadManager $downloadManager
) {
parent::__construct($appName, $request, $session, $urlGenerator);
$this->config = $config;
@ -161,6 +171,7 @@ class ShareController extends AuthPublicShareController {
$this->l10n = $l10n;
$this->defaults = $defaults;
$this->shareManager = $shareManager;
$this->downloadManager = $downloadManager;
}
/**
@ -387,14 +398,14 @@ class ShareController extends AuthPublicShareController {
$freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
}
$hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ);
$hideFileList = !($share->getPermissions() & Constants::PERMISSION_READ);
$maxUploadFilesize = $freeSpace;
$folder = new Template('files', 'list', '');
$folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
$folder->assign('dirToken', $this->getToken());
$folder->assign('permissions', \OCP\Constants::PERMISSION_READ);
$folder->assign('permissions', Constants::PERMISSION_READ);
$folder->assign('isPublic', true);
$folder->assign('hideFileList', $hideFileList);
$folder->assign('publicUploadEnabled', 'no');
@ -462,6 +473,7 @@ class ShareController extends AuthPublicShareController {
\OCP\Util::addScript('files', 'fileactionsmenu');
\OCP\Util::addScript('files', 'jquery.fileupload');
\OCP\Util::addScript('files_sharing', 'files_drop');
\OCP\Util::addScript('files', 'dist/download');
if (isset($shareTmpl['folder'])) {
// JS required for folders
@ -502,7 +514,7 @@ class ShareController extends AuthPublicShareController {
$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']]));
}
$isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== \OCP\Constants::PERMISSION_CREATE;
$isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== Constants::PERMISSION_CREATE;
if ($isNoneFileDropFolder && !$share->getHideDownload()) {
\OCP\Util::addScript('files_sharing', 'public_note');
@ -542,29 +554,54 @@ class ShareController extends AuthPublicShareController {
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @param string $token
* @param string $files
* @param string $path
* @param string $downloadStartSecret
* @return void|\OCP\AppFramework\Http\Response
* @throws NotFoundException
*/
public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
public function registerDownload(string $token, string $files = '', string $path = '', string $downloadStartSecret = ''): Response {
\OC_User::setIncognitoMode(true);
$share = $this->shareManager->getShareByToken($token);
if (!($share->getPermissions() & Constants::PERMISSION_READ)) {
return new DataResponse('Share has no read permission', Http::STATUS_FORBIDDEN);
}
if (!$this->validateShare($share)) {
return new DataResponse('Share has not been found', Http::STATUS_NOT_FOUND);
}
if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
$token = $this->downloadManager->register([
'token' => $token,
'files' => $files,
'path' => $path,
'downloadStartSecret' => $downloadStartSecret,
]);
return new JSONResponse(['token' => $token]);
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @return void|\OCP\AppFramework\Http\Response
* @throws NotFoundException
*/
public function downloadShare(string $downloadToken) {
$data = $this->downloadManager->retrieve($downloadToken);
\OC_User::setIncognitoMode(true);
$share = $this->shareManager->getShareByToken($data['token']);
if (!($share->getPermissions() & Constants::PERMISSION_READ)) {
return new DataResponse('Share has no read permission');
}
$files_list = null;
if (!is_null($files)) { // download selected files
$files_list = json_decode($files);
if (!is_null($data['files'])) { // download selected files
$files_list = json_decode($data['files']);
// in case we get only a single file
if ($files_list === null) {
$files_list = [$files];
$files_list = [$data['files']];
}
// Just in case $files is a single int like '1234'
if (!is_array($files_list)) {
@ -579,7 +616,6 @@ class ShareController extends AuthPublicShareController {
$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
// Single file share
if ($share->getNode() instanceof \OCP\Files\File) {
// Single file download
@ -591,9 +627,9 @@ class ShareController extends AuthPublicShareController {
$node = $share->getNode();
// Try to get the path
if ($path !== '') {
if ($data['path'] !== '') {
try {
$node = $node->get($path);
$node = $node->get($data['path']);
} catch (NotFoundException $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
return new NotFoundResponse();
@ -628,12 +664,12 @@ class ShareController extends AuthPublicShareController {
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*/
if (!empty($downloadStartSecret)
&& !isset($downloadStartSecret[32])
&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
if (!empty($data['downloadStartSecret'])
&& !isset($data['downloadStartSecret'][32])
&& preg_match('!^[a-zA-Z0-9]+$!', $data['downloadStartSecret']) === 1) {
// FIXME: set on the response once we use an actual app framework response
setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
setcookie('ocDownloadStarted', $data['downloadStartSecret'], time() + 20, '/');
}
$this->emitAccessShareHook($share);
@ -648,7 +684,7 @@ class ShareController extends AuthPublicShareController {
}
// download selected files
if (!is_null($files) && $files !== '') {
if (!is_null($data['files']) && $data['files'] !== '') {
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
// after dispatching the request which results in a "Cannot modify header information" notice.
OC_Files::get($originalSharePath, $files_list, $server_params);

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\BackgroundJobs;
use OC\BackgroundJob\TimedJob;
use OC\Files\Utils\DownloadManager;
use OCP\IConfig;
class CleanupDownloadTokens extends TimedJob {
private const INTERVAL_MINUTES = 24 * 60;
/** @var IConfig */
private $config;
/** @var DownloadManager */
private $downloadManager;
public function __construct(IConfig $config, DownloadManager $downloadManager) {
$this->interval = self::INTERVAL_MINUTES;
$this->config = $config;
$this->downloadManager = $downloadManager;
}
protected function run($argument) {
$this->downloadManager->cleanupTokens();
}
}

View File

@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Files\Utils;
use OCP\Files\NotFoundException;
use OCP\Files\Utils\IDownloadManager;
use OCP\IConfig;
use OCP\Security\ISecureRandom;
use RuntimeException;
use function json_decode;
use function json_encode;
class DownloadManager implements IDownloadManager {
/**
* Lifetime of tokens. Period of 2 days are chosen to allow continuation
* of downloads with network interruptions in mind
*/
protected const TOKEN_TTL = 24 * 60 * 2;
protected const TOKEN_PREFIX = 'dl_token_';
protected const FIELD_DATA = 'downloadData';
protected const FIELD_ACTIVITY = 'lastActivity';
/** @var IConfig */
private $config;
/** @var ISecureRandom */
private $secureRandom;
public function __construct(IConfig $config, ISecureRandom $secureRandom) {
$this->config = $config;
$this->secureRandom = $secureRandom;
}
/**
* @inheritDoc
*/
public function register(array $data): string {
$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('core', self::TOKEN_PREFIX . $token, '') !== '');
$this->config->setAppValue(
'core',
self::TOKEN_PREFIX . $token,
json_encode([
self::FIELD_DATA => $data,
self::FIELD_ACTIVITY => time()
])
);
return $token;
}
/**
* @inheritDoc
*/
public function retrieve(string $token): array {
$dataStr = $this->config->getAppValue('core', self::TOKEN_PREFIX . $token, '');
if ($dataStr === '') {
throw new NotFoundException();
}
$data = json_decode($dataStr, true);
$data[self::FIELD_ACTIVITY] = time();
$this->config->setAppValue('core', self::TOKEN_PREFIX . $token, json_encode($data));
return $data[self::FIELD_DATA];
}
public function cleanupTokens(): void {
$appKeys = $this->config->getAppKeys('core');
foreach ($appKeys as $key) {
if (strpos($key, self::TOKEN_PREFIX) !== 0) {
continue;
}
$dataStr = $this->config->getAppValue('core', $key, '');
if ($dataStr === '') {
$this->config->deleteAppValue('core', $key);
continue;
}
$data = json_decode($dataStr, true);
if (!isset($data[self::FIELD_ACTIVITY])
|| (time() - $data[self::FIELD_ACTIVITY]) > self::TOKEN_TTL
) {
$this->config->deleteAppValue('core', $key);
}
}
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Repair\NC21;
use OC\Core\BackgroundJobs\CleanupDownloadTokens;
use OCP\BackgroundJob\IJobList;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class AddCleanupDownloadTokenJob implements IRepairStep {
/** @var IJobList */
protected $jobList;
public function __construct(IJobList $jobList) {
$this->jobList = $jobList;
}
public function getName(): string {
return 'Add background job to cleanup download tokens';
}
public function run(IOutput $output) {
$this->jobList->add(CleanupDownloadTokens::class);
}
}

View File

@ -97,6 +97,7 @@ use OC\Files\Node\Root;
use OC\Files\Storage\StorageFactory;
use OC\Files\Template\TemplateManager;
use OC\Files\Type\Loader;
use OC\Files\Utils\DownloadManager;
use OC\Files\View;
use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
@ -170,6 +171,7 @@ use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorageFactory;
use OCP\Files\Template\ITemplateManager;
use OCP\Files\Utils\IDownloadManager;
use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\GlobalScale\IConfig;
use OCP\Group\Events\BeforeGroupCreatedEvent;
@ -1345,6 +1347,8 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class);
$this->registerAlias(IDownloadManager::class, DownloadManager::class);
$this->connectDispatcher();
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Files\Utils;
use OCP\Files\NotFoundException;
use RuntimeException;
/**
* Interface IDownloadManager
*
* The usual process is to prepare a file download via POST request. Follow
* up with a GET request providing the token for best browser integration.
* Announcing the download via POST enables to hide the payload in the
* request body rather then the URL, which also lifts limitations on
* data length.
*
* @package OCP\Files
*
* @since 22.0.0
*/
interface IDownloadManager {
/**
* Register download data and receive a token to access it later on.
*
* The provided data will be returned on retrieve() again. The structure
* is up to the consumer, it is not being processed, but only stored by
* the manager.
*
* @throws RuntimeException
* @since 22.0.0
*/
public function register(array $data): string;
/**
* Retrieves the download data for the provided token.
*
* @throws NotFoundException
* @since 22.0.0
*/
public function retrieve(string $token): array;
}