Compare commits
6 Commits
master
...
fix/noid/f
Author | SHA1 | Date |
---|---|---|
blizzz | a2a40c753d | |
Arthur Schiwon | ca81e958a5 | |
Arthur Schiwon | 397cecd089 | |
Arthur Schiwon | bc23d4eb27 | |
Arthur Schiwon | 5b326df36c | |
Arthur Schiwon | 45552c8c59 |
|
@ -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);
|
|
@ -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');
|
||||
|
|
|
@ -621,7 +621,7 @@
|
|||
};
|
||||
|
||||
context.fileList.showFileBusyState(filename, true);
|
||||
OCA.Files.Files.handleDownload(url, disableLoadingState);
|
||||
OCA.Files.Files.handleDownload(filename, dir, disableLoadingState);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue