PreviewController.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Core\Controller;
  8. use OCP\AppFramework\Controller;
  9. use OCP\AppFramework\Http;
  10. use OCP\AppFramework\Http\Attribute\FrontpageRoute;
  11. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  12. use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
  13. use OCP\AppFramework\Http\DataResponse;
  14. use OCP\AppFramework\Http\FileDisplayResponse;
  15. use OCP\AppFramework\Http\RedirectResponse;
  16. use OCP\Files\File;
  17. use OCP\Files\IRootFolder;
  18. use OCP\Files\Node;
  19. use OCP\Files\NotFoundException;
  20. use OCP\Files\Storage\ISharedStorage;
  21. use OCP\IPreview;
  22. use OCP\IRequest;
  23. use OCP\Preview\IMimeIconProvider;
  24. class PreviewController extends Controller {
  25. public function __construct(
  26. string $appName,
  27. IRequest $request,
  28. private IPreview $preview,
  29. private IRootFolder $root,
  30. private ?string $userId,
  31. private IMimeIconProvider $mimeIconProvider,
  32. ) {
  33. parent::__construct($appName, $request);
  34. }
  35. /**
  36. * Get a preview by file path
  37. *
  38. * @param string $file Path of the file
  39. * @param int $x Width of the preview. A width of -1 will use the original image width.
  40. * @param int $y Height of the preview. A height of -1 will use the original image height.
  41. * @param bool $a Preserve the aspect ratio
  42. * @param bool $forceIcon Force returning an icon
  43. * @param 'fill'|'cover' $mode How to crop the image
  44. * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
  45. * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
  46. *
  47. * 200: Preview returned
  48. * 303: Redirect to the mime icon url if mimeFallback is true
  49. * 400: Getting preview is not possible
  50. * 403: Getting preview is not allowed
  51. * 404: Preview not found
  52. */
  53. #[NoAdminRequired]
  54. #[NoCSRFRequired]
  55. #[FrontpageRoute(verb: 'GET', url: '/core/preview.png')]
  56. public function getPreview(
  57. string $file = '',
  58. int $x = 32,
  59. int $y = 32,
  60. bool $a = false,
  61. bool $forceIcon = true,
  62. string $mode = 'fill',
  63. bool $mimeFallback = false): Http\Response {
  64. if ($file === '' || $x === 0 || $y === 0) {
  65. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  66. }
  67. try {
  68. $userFolder = $this->root->getUserFolder($this->userId);
  69. $node = $userFolder->get($file);
  70. } catch (NotFoundException $e) {
  71. return new DataResponse([], Http::STATUS_NOT_FOUND);
  72. }
  73. return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
  74. }
  75. /**
  76. * Get a preview by file ID
  77. *
  78. * @param int $fileId ID of the file
  79. * @param int $x Width of the preview. A width of -1 will use the original image width.
  80. * @param int $y Height of the preview. A height of -1 will use the original image height.
  81. * @param bool $a Preserve the aspect ratio
  82. * @param bool $forceIcon Force returning an icon
  83. * @param 'fill'|'cover' $mode How to crop the image
  84. * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
  85. * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
  86. *
  87. * 200: Preview returned
  88. * 303: Redirect to the mime icon url if mimeFallback is true
  89. * 400: Getting preview is not possible
  90. * 403: Getting preview is not allowed
  91. * 404: Preview not found
  92. */
  93. #[NoAdminRequired]
  94. #[NoCSRFRequired]
  95. #[FrontpageRoute(verb: 'GET', url: '/core/preview')]
  96. public function getPreviewByFileId(
  97. int $fileId = -1,
  98. int $x = 32,
  99. int $y = 32,
  100. bool $a = false,
  101. bool $forceIcon = true,
  102. string $mode = 'fill',
  103. bool $mimeFallback = false) {
  104. if ($fileId === -1 || $x === 0 || $y === 0) {
  105. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  106. }
  107. $userFolder = $this->root->getUserFolder($this->userId);
  108. $node = $userFolder->getFirstNodeById($fileId);
  109. if (!$node) {
  110. return new DataResponse([], Http::STATUS_NOT_FOUND);
  111. }
  112. return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
  113. }
  114. /**
  115. * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
  116. */
  117. private function fetchPreview(
  118. Node $node,
  119. int $x,
  120. int $y,
  121. bool $a,
  122. bool $forceIcon,
  123. string $mode,
  124. bool $mimeFallback = false) : Http\Response {
  125. if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) {
  126. return new DataResponse([], Http::STATUS_NOT_FOUND);
  127. }
  128. if (!$node->isReadable()) {
  129. return new DataResponse([], Http::STATUS_FORBIDDEN);
  130. }
  131. if ($node->getId() <= 0) {
  132. return new DataResponse([], Http::STATUS_NOT_FOUND);
  133. }
  134. // Is this header is set it means our UI is doing a preview for no-download shares
  135. // we check a header so we at least prevent people from using the link directly (obfuscation)
  136. $isNextcloudPreview = $this->request->getHeader('X-NC-Preview') === 'true';
  137. $storage = $node->getStorage();
  138. if ($isNextcloudPreview === false && $storage->instanceOfStorage(ISharedStorage::class)) {
  139. /** @var ISharedStorage $storage */
  140. $share = $storage->getShare();
  141. $attributes = $share->getAttributes();
  142. // No "allow preview" header set, so we must check if
  143. // the share has not explicitly disabled download permissions
  144. if ($attributes?->getAttribute('permissions', 'download') === false) {
  145. return new DataResponse([], Http::STATUS_FORBIDDEN);
  146. }
  147. }
  148. try {
  149. $f = $this->preview->getPreview($node, $x, $y, !$a, $mode);
  150. $response = new FileDisplayResponse($f, Http::STATUS_OK, [
  151. 'Content-Type' => $f->getMimeType(),
  152. ]);
  153. $response->cacheFor(3600 * 24, false, true);
  154. return $response;
  155. } catch (NotFoundException $e) {
  156. // If we have no preview enabled, we can redirect to the mime icon if any
  157. if ($mimeFallback) {
  158. if ($url = $this->mimeIconProvider->getMimeIconUrl($node->getMimeType())) {
  159. return new RedirectResponse($url);
  160. }
  161. }
  162. return new DataResponse([], Http::STATUS_NOT_FOUND);
  163. } catch (\InvalidArgumentException $e) {
  164. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  165. }
  166. }
  167. }