PublicPreviewController.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OCA\Files_Sharing\Controller;
  7. use OCP\AppFramework\Http;
  8. use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
  9. use OCP\AppFramework\Http\Attribute\PublicPage;
  10. use OCP\AppFramework\Http\DataResponse;
  11. use OCP\AppFramework\Http\FileDisplayResponse;
  12. use OCP\AppFramework\PublicShareController;
  13. use OCP\Constants;
  14. use OCP\Files\Folder;
  15. use OCP\Files\NotFoundException;
  16. use OCP\IPreview;
  17. use OCP\IRequest;
  18. use OCP\ISession;
  19. use OCP\Share\Exceptions\ShareNotFound;
  20. use OCP\Share\IManager as ShareManager;
  21. use OCP\Share\IShare;
  22. class PublicPreviewController extends PublicShareController {
  23. /** @var IShare */
  24. private $share;
  25. public function __construct(
  26. string $appName,
  27. IRequest $request,
  28. private ShareManager $shareManager,
  29. ISession $session,
  30. private IPreview $previewManager,
  31. ) {
  32. parent::__construct($appName, $request, $session);
  33. }
  34. protected function getPasswordHash(): ?string {
  35. return $this->share->getPassword();
  36. }
  37. public function isValidToken(): bool {
  38. try {
  39. $this->share = $this->shareManager->getShareByToken($this->getToken());
  40. return true;
  41. } catch (ShareNotFound $e) {
  42. return false;
  43. }
  44. }
  45. protected function isPasswordProtected(): bool {
  46. return $this->share->getPassword() !== null;
  47. }
  48. /**
  49. * Get a preview for a shared file
  50. *
  51. * @param string $token Token of the share
  52. * @param string $file File in the share
  53. * @param int $x Width of the preview
  54. * @param int $y Height of the preview
  55. * @param bool $a Whether to not crop the preview
  56. * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
  57. *
  58. * 200: Preview returned
  59. * 400: Getting preview is not possible
  60. * 403: Getting preview is not allowed
  61. * 404: Share or preview not found
  62. */
  63. #[PublicPage]
  64. #[NoCSRFRequired]
  65. public function getPreview(
  66. string $token,
  67. string $file = '',
  68. int $x = 32,
  69. int $y = 32,
  70. $a = false,
  71. ) {
  72. $cacheForSeconds = 60 * 60 * 24; // 1 day
  73. if ($token === '' || $x === 0 || $y === 0) {
  74. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  75. }
  76. try {
  77. $share = $this->shareManager->getShareByToken($token);
  78. } catch (ShareNotFound $e) {
  79. return new DataResponse([], Http::STATUS_NOT_FOUND);
  80. }
  81. if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
  82. return new DataResponse([], Http::STATUS_FORBIDDEN);
  83. }
  84. $attributes = $share->getAttributes();
  85. // Only explicitly set to false will forbid the download!
  86. $downloadForbidden = $attributes?->getAttribute('permissions', 'download') === false;
  87. // Is this header is set it means our UI is doing a preview for no-download shares
  88. // we check a header so we at least prevent people from using the link directly (obfuscation)
  89. $isPublicPreview = $this->request->getHeader('X-NC-Preview') === 'true';
  90. if ($isPublicPreview && $downloadForbidden) {
  91. // Only cache for 15 minutes on public preview requests to quickly remove from cache
  92. $cacheForSeconds = 15 * 60;
  93. } elseif ($downloadForbidden) {
  94. // This is not a public share preview so we only allow a preview if download permissions are granted
  95. return new DataResponse([], Http::STATUS_FORBIDDEN);
  96. }
  97. try {
  98. $node = $share->getNode();
  99. if ($node instanceof Folder) {
  100. $file = $node->get($file);
  101. } else {
  102. $file = $node;
  103. }
  104. $f = $this->previewManager->getPreview($file, $x, $y, !$a);
  105. $response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
  106. $response->cacheFor($cacheForSeconds);
  107. return $response;
  108. } catch (NotFoundException $e) {
  109. return new DataResponse([], Http::STATUS_NOT_FOUND);
  110. } catch (\InvalidArgumentException $e) {
  111. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  112. }
  113. }
  114. /**
  115. * @NoSameSiteCookieRequired
  116. *
  117. * Get a direct link preview for a shared file
  118. *
  119. * @param string $token Token of the share
  120. * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
  121. *
  122. * 200: Preview returned
  123. * 400: Getting preview is not possible
  124. * 403: Getting preview is not allowed
  125. * 404: Share or preview not found
  126. */
  127. #[PublicPage]
  128. #[NoCSRFRequired]
  129. public function directLink(string $token) {
  130. // No token no image
  131. if ($token === '') {
  132. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  133. }
  134. // No share no image
  135. try {
  136. $share = $this->shareManager->getShareByToken($token);
  137. } catch (ShareNotFound $e) {
  138. return new DataResponse([], Http::STATUS_NOT_FOUND);
  139. }
  140. // No permissions no image
  141. if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
  142. return new DataResponse([], Http::STATUS_FORBIDDEN);
  143. }
  144. // Password protected shares have no direct link!
  145. if ($share->getPassword() !== null) {
  146. return new DataResponse([], Http::STATUS_FORBIDDEN);
  147. }
  148. $attributes = $share->getAttributes();
  149. if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
  150. return new DataResponse([], Http::STATUS_FORBIDDEN);
  151. }
  152. try {
  153. $node = $share->getNode();
  154. if ($node instanceof Folder) {
  155. // Direct link only works for single files
  156. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  157. }
  158. $f = $this->previewManager->getPreview($node, -1, -1, false);
  159. $response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
  160. $response->cacheFor(3600 * 24);
  161. return $response;
  162. } catch (NotFoundException $e) {
  163. return new DataResponse([], Http::STATUS_NOT_FOUND);
  164. } catch (\InvalidArgumentException $e) {
  165. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  166. }
  167. }
  168. }