PreviewController.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 OCA\Files_Sharing\SharedStorage;
  9. use OCP\AppFramework\Controller;
  10. use OCP\AppFramework\Http;
  11. use OCP\AppFramework\Http\Attribute\FrontpageRoute;
  12. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  13. use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
  14. use OCP\AppFramework\Http\DataResponse;
  15. use OCP\AppFramework\Http\FileDisplayResponse;
  16. use OCP\AppFramework\Http\RedirectResponse;
  17. use OCP\Files\File;
  18. use OCP\Files\IRootFolder;
  19. use OCP\Files\Node;
  20. use OCP\Files\NotFoundException;
  21. use OCP\IConfig;
  22. use OCP\IPreview;
  23. use OCP\IRequest;
  24. use OCP\Preview\IMimeIconProvider;
  25. class PreviewController extends Controller {
  26. public function __construct(
  27. string $appName,
  28. IRequest $request,
  29. private IPreview $preview,
  30. private IRootFolder $root,
  31. private ?string $userId,
  32. private IMimeIconProvider $mimeIconProvider,
  33. private IConfig $config,
  34. ) {
  35. parent::__construct($appName, $request);
  36. }
  37. /**
  38. * Get a preview by file path
  39. *
  40. * @param string $file Path of the file
  41. * @param int $x Width of the preview. A width of -1 will use the original image width.
  42. * @param int $y Height of the preview. A height of -1 will use the original image height.
  43. * @param bool $a Preserve the aspect ratio
  44. * @param bool $forceIcon Force returning an icon
  45. * @param 'fill'|'cover' $mode How to crop the image
  46. * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
  47. * @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{}>
  48. *
  49. * 200: Preview returned
  50. * 303: Redirect to the mime icon url if mimeFallback is true
  51. * 400: Getting preview is not possible
  52. * 403: Getting preview is not allowed
  53. * 404: Preview not found
  54. */
  55. #[NoAdminRequired]
  56. #[NoCSRFRequired]
  57. #[FrontpageRoute(verb: 'GET', url: '/core/preview.png')]
  58. public function getPreview(
  59. string $file = '',
  60. int $x = 32,
  61. int $y = 32,
  62. bool $a = false,
  63. bool $forceIcon = true,
  64. string $mode = 'fill',
  65. bool $mimeFallback = false): Http\Response {
  66. if ($file === '' || $x === 0 || $y === 0) {
  67. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  68. }
  69. try {
  70. $userFolder = $this->root->getUserFolder($this->userId);
  71. $node = $userFolder->get($file);
  72. } catch (NotFoundException $e) {
  73. return new DataResponse([], Http::STATUS_NOT_FOUND);
  74. }
  75. return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
  76. }
  77. /**
  78. * Get a preview by file ID
  79. *
  80. * @param int $fileId ID of the file
  81. * @param int $x Width of the preview. A width of -1 will use the original image width.
  82. * @param int $y Height of the preview. A height of -1 will use the original image height.
  83. * @param bool $a Preserve the aspect ratio
  84. * @param bool $forceIcon Force returning an icon
  85. * @param 'fill'|'cover' $mode How to crop the image
  86. * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
  87. * @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{}>
  88. *
  89. * 200: Preview returned
  90. * 303: Redirect to the mime icon url if mimeFallback is true
  91. * 400: Getting preview is not possible
  92. * 403: Getting preview is not allowed
  93. * 404: Preview not found
  94. */
  95. #[NoAdminRequired]
  96. #[NoCSRFRequired]
  97. #[FrontpageRoute(verb: 'GET', url: '/core/preview')]
  98. public function getPreviewByFileId(
  99. int $fileId = -1,
  100. int $x = 32,
  101. int $y = 32,
  102. bool $a = false,
  103. bool $forceIcon = true,
  104. string $mode = 'fill',
  105. bool $mimeFallback = false) {
  106. if ($fileId === -1 || $x === 0 || $y === 0) {
  107. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  108. }
  109. $userFolder = $this->root->getUserFolder($this->userId);
  110. $node = $userFolder->getFirstNodeById($fileId);
  111. if (!$node) {
  112. return new DataResponse([], Http::STATUS_NOT_FOUND);
  113. }
  114. return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
  115. }
  116. /**
  117. * @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{}>
  118. */
  119. private function fetchPreview(
  120. Node $node,
  121. int $x,
  122. int $y,
  123. bool $a,
  124. bool $forceIcon,
  125. string $mode,
  126. bool $mimeFallback = false) : Http\Response {
  127. if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) {
  128. return new DataResponse([], Http::STATUS_NOT_FOUND);
  129. }
  130. if (!$node->isReadable()) {
  131. return new DataResponse([], Http::STATUS_FORBIDDEN);
  132. }
  133. if ($node->getId() <= 0) {
  134. return new DataResponse([], Http::STATUS_NOT_FOUND);
  135. }
  136. /** @var SharedStorage $storage */
  137. $storage = $node->getStorage();
  138. if ($storage->instanceOfStorage(SharedStorage::class)) {
  139. $share = $storage->getShare();
  140. $allowedFileExtensions = $this->config->getSystemValue('allowed_view_extensions', []);
  141. $isAllowedToViewForExtension = $allowedFileExtensions && in_array($node->getExtension(), $allowedFileExtensions, true);
  142. $shareAttributes = $share->getAttributes();
  143. $isAllowedByShare = $shareAttributes === null || $shareAttributes->getAttribute('permissions', 'download') !== false;
  144. if (!$isAllowedToViewForExtension && !$isAllowedByShare) {
  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. }