diff options
Diffstat (limited to 'core/Controller/PreviewController.php')
-rw-r--r-- | core/Controller/PreviewController.php | 160 |
1 files changed, 105 insertions, 55 deletions
diff --git a/core/Controller/PreviewController.php b/core/Controller/PreviewController.php index 118f1b47752..aac49c06d57 100644 --- a/core/Controller/PreviewController.php +++ b/core/Controller/PreviewController.php @@ -3,73 +3,73 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Core\Controller; -use OCA\Files_Sharing\SharedStorage; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\FrontpageRoute; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\OpenAPI; +use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\Response; use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\Files\Storage\ISharedStorage; use OCP\IPreview; use OCP\IRequest; +use OCP\Preview\IMimeIconProvider; class PreviewController extends Controller { - private ?string $userId; - private IRootFolder $root; - private IPreview $preview; - - public function __construct(string $appName, - IRequest $request, - IPreview $preview, - IRootFolder $root, - ?string $userId + public function __construct( + string $appName, + IRequest $request, + private IPreview $preview, + private IRootFolder $root, + private ?string $userId, + private IMimeIconProvider $mimeIconProvider, ) { parent::__construct($appName, $request); - - $this->preview = $preview; - $this->root = $root; - $this->userId = $userId; } /** - * @NoAdminRequired - * @NoCSRFRequired + * Get a preview by file path + * + * @param string $file Path of the file + * @param int $x Width of the preview. A width of -1 will use the original image width. + * @param int $y Height of the preview. A height of -1 will use the original image height. + * @param bool $a Preserve the aspect ratio + * @param bool $forceIcon Force returning an icon + * @param 'fill'|'cover' $mode How to crop the image + * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> * - * @return DataResponse|FileDisplayResponse + * 200: Preview returned + * 303: Redirect to the mime icon url if mimeFallback is true + * 400: Getting preview is not possible + * 403: Getting preview is not allowed + * 404: Preview not found */ + #[NoAdminRequired] + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/core/preview.png')] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function getPreview( string $file = '', int $x = 32, int $y = 32, bool $a = false, bool $forceIcon = true, - string $mode = 'fill'): Http\Response { + string $mode = 'fill', + bool $mimeFallback = false): Response { if ($file === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } @@ -81,40 +81,55 @@ class PreviewController extends Controller { return new DataResponse([], Http::STATUS_NOT_FOUND); } - return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode); + return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback); } /** - * @NoAdminRequired - * @NoCSRFRequired + * Get a preview by file ID + * + * @param int $fileId ID of the file + * @param int $x Width of the preview. A width of -1 will use the original image width. + * @param int $y Height of the preview. A height of -1 will use the original image height. + * @param bool $a Preserve the aspect ratio + * @param bool $forceIcon Force returning an icon + * @param 'fill'|'cover' $mode How to crop the image + * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> * - * @return DataResponse|FileDisplayResponse + * 200: Preview returned + * 303: Redirect to the mime icon url if mimeFallback is true + * 400: Getting preview is not possible + * 403: Getting preview is not allowed + * 404: Preview not found */ + #[NoAdminRequired] + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/core/preview')] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function getPreviewByFileId( int $fileId = -1, int $x = 32, int $y = 32, bool $a = false, bool $forceIcon = true, - string $mode = 'fill') { + string $mode = 'fill', + bool $mimeFallback = false) { if ($fileId === -1 || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } $userFolder = $this->root->getUserFolder($this->userId); - $nodes = $userFolder->getById($fileId); + $node = $userFolder->getFirstNodeById($fileId); - if (\count($nodes) === 0) { + if (!$node) { return new DataResponse([], Http::STATUS_NOT_FOUND); } - $node = array_pop($nodes); - - return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode); + return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback); } /** - * @return DataResponse|FileDisplayResponse + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> */ private function fetchPreview( Node $node, @@ -122,7 +137,8 @@ class PreviewController extends Controller { int $y, bool $a, bool $forceIcon, - string $mode) : Http\Response { + string $mode, + bool $mimeFallback = false) : Response { if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) { return new DataResponse([], Http::STATUS_NOT_FOUND); } @@ -130,12 +146,18 @@ class PreviewController extends Controller { return new DataResponse([], Http::STATUS_FORBIDDEN); } + if ($node->getId() <= 0) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + // Is this header is set it means our UI is doing a preview for no-download shares + // we check a header so we at least prevent people from using the link directly (obfuscation) + $isNextcloudPreview = $this->request->getHeader('x-nc-preview') === 'true'; $storage = $node->getStorage(); - if ($storage->instanceOfStorage(SharedStorage::class)) { - /** @var SharedStorage $storage */ + if ($isNextcloudPreview === false && $storage->instanceOfStorage(ISharedStorage::class)) { + /** @var ISharedStorage $storage */ $share = $storage->getShare(); - $attributes = $share->getAttributes(); - if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) { + if (!$share->canSeeContent()) { return new DataResponse([], Http::STATUS_FORBIDDEN); } } @@ -148,9 +170,37 @@ class PreviewController extends Controller { $response->cacheFor(3600 * 24, false, true); return $response; } catch (NotFoundException $e) { + // If we have no preview enabled, we can redirect to the mime icon if any + if ($mimeFallback) { + if ($url = $this->mimeIconProvider->getMimeIconUrl($node->getMimeType())) { + return new RedirectResponse($url); + } + } + return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (\InvalidArgumentException $e) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } } + + /** + * Get a preview by mime + * + * @param string $mime Mime type + * @return RedirectResponse<Http::STATUS_SEE_OTHER, array{}> + * + * 303: The mime icon url + */ + #[NoCSRFRequired] + #[PublicPage] + #[FrontpageRoute(verb: 'GET', url: '/core/mimeicon')] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] + public function getMimeIconUrl(string $mime = 'application/octet-stream') { + $url = $this->mimeIconProvider->getMimeIconUrl($mime); + if ($url === null) { + $url = $this->mimeIconProvider->getMimeIconUrl('application/octet-stream'); + } + + return new RedirectResponse($url); + } } |