ShareController.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
  10. * @author Georg Ehrke <oc.list@georgehrke.com>
  11. * @author j3l11234 <297259024@qq.com>
  12. * @author Joas Schilling <coding@schilljs.com>
  13. * @author John Molakvoæ <skjnldsv@protonmail.com>
  14. * @author Jonas Sulzer <jonas@violoncello.ch>
  15. * @author Julius Härtl <jus@bitgrid.net>
  16. * @author Lukas Reschke <lukas@statuscode.ch>
  17. * @author MartB <mart.b@outlook.de>
  18. * @author Maxence Lange <maxence@pontapreta.net>
  19. * @author Michael Weimann <mail@michael-weimann.eu>
  20. * @author Morris Jobke <hey@morrisjobke.de>
  21. * @author Piotr Filiciak <piotr@filiciak.pl>
  22. * @author Robin Appelman <robin@icewind.nl>
  23. * @author Roeland Jago Douma <roeland@famdouma.nl>
  24. * @author Sascha Sambale <mastixmc@gmail.com>
  25. * @author Thomas Müller <thomas.mueller@tmit.eu>
  26. * @author Vincent Petry <vincent@nextcloud.com>
  27. *
  28. * @license AGPL-3.0
  29. *
  30. * This code is free software: you can redistribute it and/or modify
  31. * it under the terms of the GNU Affero General Public License, version 3,
  32. * as published by the Free Software Foundation.
  33. *
  34. * This program is distributed in the hope that it will be useful,
  35. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  36. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  37. * GNU Affero General Public License for more details.
  38. *
  39. * You should have received a copy of the GNU Affero General Public License, version 3,
  40. * along with this program. If not, see <http://www.gnu.org/licenses/>
  41. *
  42. */
  43. namespace OCA\Files_Sharing\Controller;
  44. use OC\Security\CSP\ContentSecurityPolicy;
  45. use OC_Files;
  46. use OC_Util;
  47. use OCA\FederatedFileSharing\FederatedShareProvider;
  48. use OCA\Files_Sharing\Activity\Providers\Downloads;
  49. use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
  50. use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
  51. use OCA\Viewer\Event\LoadViewer;
  52. use OCP\Accounts\IAccountManager;
  53. use OCP\AppFramework\AuthPublicShareController;
  54. use OCP\AppFramework\Http\NotFoundResponse;
  55. use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
  56. use OCP\AppFramework\Http\Template\LinkMenuAction;
  57. use OCP\AppFramework\Http\Template\PublicTemplateResponse;
  58. use OCP\AppFramework\Http\Template\SimpleMenuAction;
  59. use OCP\AppFramework\Http\TemplateResponse;
  60. use OCP\Defaults;
  61. use OCP\EventDispatcher\IEventDispatcher;
  62. use OCP\Files\Folder;
  63. use OCP\Files\IRootFolder;
  64. use OCP\Files\NotFoundException;
  65. use OCP\IConfig;
  66. use OCP\IL10N;
  67. use OCP\ILogger;
  68. use OCP\IPreview;
  69. use OCP\IRequest;
  70. use OCP\ISession;
  71. use OCP\IURLGenerator;
  72. use OCP\IUser;
  73. use OCP\IUserManager;
  74. use OCP\Security\ISecureRandom;
  75. use OCP\Share;
  76. use OCP\Share\Exceptions\ShareNotFound;
  77. use OCP\Share\IManager as ShareManager;
  78. use OCP\Share\IShare;
  79. use OCP\Template;
  80. /**
  81. * Class ShareController
  82. *
  83. * @package OCA\Files_Sharing\Controllers
  84. */
  85. class ShareController extends AuthPublicShareController {
  86. protected IConfig $config;
  87. protected IUserManager $userManager;
  88. protected ILogger $logger;
  89. protected \OCP\Activity\IManager $activityManager;
  90. protected IPreview $previewManager;
  91. protected IRootFolder $rootFolder;
  92. protected FederatedShareProvider $federatedShareProvider;
  93. protected IAccountManager $accountManager;
  94. protected IEventDispatcher $eventDispatcher;
  95. protected IL10N $l10n;
  96. protected Defaults $defaults;
  97. protected ShareManager $shareManager;
  98. protected ISecureRandom $secureRandom;
  99. protected ?Share\IShare $share = null;
  100. public function __construct(string $appName,
  101. IRequest $request,
  102. IConfig $config,
  103. IURLGenerator $urlGenerator,
  104. IUserManager $userManager,
  105. ILogger $logger,
  106. \OCP\Activity\IManager $activityManager,
  107. ShareManager $shareManager,
  108. ISession $session,
  109. IPreview $previewManager,
  110. IRootFolder $rootFolder,
  111. FederatedShareProvider $federatedShareProvider,
  112. IAccountManager $accountManager,
  113. IEventDispatcher $eventDispatcher,
  114. IL10N $l10n,
  115. ISecureRandom $secureRandom,
  116. Defaults $defaults) {
  117. parent::__construct($appName, $request, $session, $urlGenerator);
  118. $this->config = $config;
  119. $this->userManager = $userManager;
  120. $this->logger = $logger;
  121. $this->activityManager = $activityManager;
  122. $this->previewManager = $previewManager;
  123. $this->rootFolder = $rootFolder;
  124. $this->federatedShareProvider = $federatedShareProvider;
  125. $this->accountManager = $accountManager;
  126. $this->eventDispatcher = $eventDispatcher;
  127. $this->l10n = $l10n;
  128. $this->secureRandom = $secureRandom;
  129. $this->defaults = $defaults;
  130. $this->shareManager = $shareManager;
  131. }
  132. public const SHARE_ACCESS = 'access';
  133. public const SHARE_AUTH = 'auth';
  134. public const SHARE_DOWNLOAD = 'download';
  135. /**
  136. * @PublicPage
  137. * @NoCSRFRequired
  138. *
  139. * Show the authentication page
  140. * The form has to submit to the authenticate method route
  141. */
  142. public function showAuthenticate(): TemplateResponse {
  143. $templateParameters = ['share' => $this->share];
  144. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
  145. $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
  146. if ($this->share->getSendPasswordByTalk()) {
  147. $csp = new ContentSecurityPolicy();
  148. $csp->addAllowedConnectDomain('*');
  149. $csp->addAllowedMediaDomain('blob:');
  150. $response->setContentSecurityPolicy($csp);
  151. }
  152. return $response;
  153. }
  154. /**
  155. * The template to show when authentication failed
  156. */
  157. protected function showAuthFailed(): TemplateResponse {
  158. $templateParameters = ['share' => $this->share, 'wrongpw' => true];
  159. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
  160. $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
  161. if ($this->share->getSendPasswordByTalk()) {
  162. $csp = new ContentSecurityPolicy();
  163. $csp->addAllowedConnectDomain('*');
  164. $csp->addAllowedMediaDomain('blob:');
  165. $response->setContentSecurityPolicy($csp);
  166. }
  167. return $response;
  168. }
  169. /**
  170. * The template to show after user identification
  171. */
  172. protected function showIdentificationResult(bool $success = false): TemplateResponse {
  173. $templateParameters = ['share' => $this->share, 'identityOk' => $success];
  174. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
  175. $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
  176. if ($this->share->getSendPasswordByTalk()) {
  177. $csp = new ContentSecurityPolicy();
  178. $csp->addAllowedConnectDomain('*');
  179. $csp->addAllowedMediaDomain('blob:');
  180. $response->setContentSecurityPolicy($csp);
  181. }
  182. return $response;
  183. }
  184. /**
  185. * Validate the identity token of a public share
  186. *
  187. * @param ?string $identityToken
  188. * @return bool
  189. */
  190. protected function validateIdentity(?string $identityToken = null): bool {
  191. if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
  192. return false;
  193. }
  194. if ($identityToken === null || $this->share->getSharedWith() === null) {
  195. return false;
  196. }
  197. return $identityToken === $this->share->getSharedWith();
  198. }
  199. /**
  200. * Generates a password for the share, respecting any password policy defined
  201. */
  202. protected function generatePassword(): void {
  203. $event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
  204. $this->eventDispatcher->dispatchTyped($event);
  205. $password = $event->getPassword() ?? $this->secureRandom->generate(20);
  206. $this->share->setPassword($password);
  207. $this->shareManager->updateShare($this->share);
  208. }
  209. protected function verifyPassword(string $password): bool {
  210. return $this->shareManager->checkPassword($this->share, $password);
  211. }
  212. protected function getPasswordHash(): string {
  213. return $this->share->getPassword();
  214. }
  215. public function isValidToken(): bool {
  216. try {
  217. $this->share = $this->shareManager->getShareByToken($this->getToken());
  218. } catch (ShareNotFound $e) {
  219. return false;
  220. }
  221. return true;
  222. }
  223. protected function isPasswordProtected(): bool {
  224. return $this->share->getPassword() !== null;
  225. }
  226. protected function authSucceeded() {
  227. // For share this was always set so it is still used in other apps
  228. $this->session->set('public_link_authenticated', (string)$this->share->getId());
  229. }
  230. protected function authFailed() {
  231. $this->emitAccessShareHook($this->share, 403, 'Wrong password');
  232. $this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
  233. }
  234. /**
  235. * throws hooks when a share is attempted to be accessed
  236. *
  237. * @param \OCP\Share\IShare|string $share the Share instance if available,
  238. * otherwise token
  239. * @param int $errorCode
  240. * @param string $errorMessage
  241. *
  242. * @throws \OCP\HintException
  243. * @throws \OC\ServerNotAvailableException
  244. *
  245. * @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
  246. */
  247. protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
  248. $itemType = $itemSource = $uidOwner = '';
  249. $token = $share;
  250. $exception = null;
  251. if ($share instanceof \OCP\Share\IShare) {
  252. try {
  253. $token = $share->getToken();
  254. $uidOwner = $share->getSharedBy();
  255. $itemType = $share->getNodeType();
  256. $itemSource = $share->getNodeId();
  257. } catch (\Exception $e) {
  258. // we log what we know and pass on the exception afterwards
  259. $exception = $e;
  260. }
  261. }
  262. \OC_Hook::emit(Share::class, 'share_link_access', [
  263. 'itemType' => $itemType,
  264. 'itemSource' => $itemSource,
  265. 'uidOwner' => $uidOwner,
  266. 'token' => $token,
  267. 'errorCode' => $errorCode,
  268. 'errorMessage' => $errorMessage
  269. ]);
  270. if (!is_null($exception)) {
  271. throw $exception;
  272. }
  273. }
  274. /**
  275. * Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
  276. */
  277. protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
  278. if ($step !== self::SHARE_ACCESS &&
  279. $step !== self::SHARE_AUTH &&
  280. $step !== self::SHARE_DOWNLOAD) {
  281. return;
  282. }
  283. $this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
  284. }
  285. /**
  286. * Validate the permissions of the share
  287. *
  288. * @param Share\IShare $share
  289. * @return bool
  290. */
  291. private function validateShare(\OCP\Share\IShare $share) {
  292. // If the owner is disabled no access to the link is granted
  293. $owner = $this->userManager->get($share->getShareOwner());
  294. if ($owner === null || !$owner->isEnabled()) {
  295. return false;
  296. }
  297. // If the initiator of the share is disabled no access is granted
  298. $initiator = $this->userManager->get($share->getSharedBy());
  299. if ($initiator === null || !$initiator->isEnabled()) {
  300. return false;
  301. }
  302. return $share->getNode()->isReadable() && $share->getNode()->isShareable();
  303. }
  304. /**
  305. * @PublicPage
  306. * @NoCSRFRequired
  307. *
  308. *
  309. * @param string $path
  310. * @return TemplateResponse
  311. * @throws NotFoundException
  312. * @throws \Exception
  313. */
  314. public function showShare($path = ''): TemplateResponse {
  315. \OC_User::setIncognitoMode(true);
  316. // Check whether share exists
  317. try {
  318. $share = $this->shareManager->getShareByToken($this->getToken());
  319. } catch (ShareNotFound $e) {
  320. // The share does not exists, we do not emit an ShareLinkAccessedEvent
  321. $this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
  322. throw new NotFoundException();
  323. }
  324. if (!$this->validateShare($share)) {
  325. throw new NotFoundException();
  326. }
  327. $shareNode = $share->getNode();
  328. // We can't get the path of a file share
  329. try {
  330. if ($shareNode instanceof \OCP\Files\File && $path !== '') {
  331. $this->emitAccessShareHook($share, 404, 'Share not found');
  332. $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
  333. throw new NotFoundException();
  334. }
  335. } catch (\Exception $e) {
  336. $this->emitAccessShareHook($share, 404, 'Share not found');
  337. $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
  338. throw $e;
  339. }
  340. $shareTmpl = [];
  341. $shareTmpl['owner'] = '';
  342. $shareTmpl['shareOwner'] = '';
  343. $owner = $this->userManager->get($share->getShareOwner());
  344. if ($owner instanceof IUser) {
  345. $ownerAccount = $this->accountManager->getAccount($owner);
  346. $ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
  347. if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) {
  348. $shareTmpl['owner'] = $owner->getUID();
  349. $shareTmpl['shareOwner'] = $owner->getDisplayName();
  350. }
  351. }
  352. $shareTmpl['filename'] = $shareNode->getName();
  353. $shareTmpl['directory_path'] = $share->getTarget();
  354. $shareTmpl['note'] = $share->getNote();
  355. $shareTmpl['mimetype'] = $shareNode->getMimetype();
  356. $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype());
  357. $shareTmpl['dirToken'] = $this->getToken();
  358. $shareTmpl['sharingToken'] = $this->getToken();
  359. $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
  360. $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
  361. $shareTmpl['dir'] = '';
  362. $shareTmpl['nonHumanFileSize'] = $shareNode->getSize();
  363. $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($shareNode->getSize());
  364. $shareTmpl['hideDownload'] = $share->getHideDownload();
  365. $hideFileList = false;
  366. if ($shareNode instanceof \OCP\Files\Folder) {
  367. $shareIsFolder = true;
  368. try {
  369. $folderNode = $shareNode->get($path);
  370. } catch (\OCP\Files\NotFoundException $e) {
  371. $this->emitAccessShareHook($share, 404, 'Share not found');
  372. $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
  373. throw new NotFoundException();
  374. }
  375. $shareTmpl['dir'] = $shareNode->getRelativePath($folderNode->getPath());
  376. /*
  377. * The OC_Util methods require a view. This just uses the node API
  378. */
  379. $freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath());
  380. if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
  381. $freeSpace = max($freeSpace, 0);
  382. } else {
  383. $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
  384. }
  385. $hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ);
  386. $maxUploadFilesize = $freeSpace;
  387. $folder = new Template('files', 'list', '');
  388. $folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
  389. $folder->assign('dirToken', $this->getToken());
  390. $folder->assign('permissions', \OCP\Constants::PERMISSION_READ);
  391. $folder->assign('isPublic', true);
  392. $folder->assign('hideFileList', $hideFileList);
  393. $folder->assign('publicUploadEnabled', 'no');
  394. // default to list view
  395. $folder->assign('showgridview', false);
  396. $folder->assign('uploadMaxFilesize', $maxUploadFilesize);
  397. $folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize));
  398. $folder->assign('freeSpace', $freeSpace);
  399. $folder->assign('usedSpacePercent', 0);
  400. $folder->assign('trash', false);
  401. $shareTmpl['folder'] = $folder->fetchPage();
  402. } else {
  403. $shareIsFolder = false;
  404. }
  405. // default to list view
  406. $shareTmpl['showgridview'] = false;
  407. $shareTmpl['hideFileList'] = $hideFileList;
  408. $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', [
  409. 'token' => $this->getToken(),
  410. 'filename' => $shareIsFolder ? null : $shareNode->getName()
  411. ]);
  412. $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $this->getToken()]);
  413. $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
  414. $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
  415. $shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
  416. $shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
  417. $shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null);
  418. $shareTmpl['previewURL'] = $shareTmpl['downloadURL'];
  419. if ($shareTmpl['previewSupported']) {
  420. $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview',
  421. ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
  422. $ogPreview = $shareTmpl['previewImage'];
  423. // We just have direct previews for image files
  424. if ($shareNode->getMimePart() === 'image') {
  425. $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $this->getToken()]);
  426. $ogPreview = $shareTmpl['previewURL'];
  427. //Whatapp is kind of picky about their size requirements
  428. if ($this->request->isUserAgent(['/^WhatsApp/'])) {
  429. $ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
  430. 'token' => $this->getToken(),
  431. 'x' => 256,
  432. 'y' => 256,
  433. 'a' => true,
  434. ]);
  435. }
  436. }
  437. } else {
  438. $shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
  439. $ogPreview = $shareTmpl['previewImage'];
  440. }
  441. // Load files we need
  442. \OCP\Util::addScript('files', 'semaphore');
  443. \OCP\Util::addScript('files', 'file-upload');
  444. \OCP\Util::addStyle('files_sharing', 'publicView');
  445. \OCP\Util::addScript('files_sharing', 'public');
  446. \OCP\Util::addScript('files_sharing', 'templates');
  447. \OCP\Util::addScript('files', 'fileactions');
  448. \OCP\Util::addScript('files', 'fileactionsmenu');
  449. \OCP\Util::addScript('files', 'jquery.fileupload');
  450. \OCP\Util::addScript('files_sharing', 'files_drop');
  451. if (isset($shareTmpl['folder'])) {
  452. // JS required for folders
  453. \OCP\Util::addStyle('files', 'merged');
  454. \OCP\Util::addScript('files', 'filesummary');
  455. \OCP\Util::addScript('files', 'templates');
  456. \OCP\Util::addScript('files', 'breadcrumb');
  457. \OCP\Util::addScript('files', 'fileinfomodel');
  458. \OCP\Util::addScript('files', 'newfilemenu');
  459. \OCP\Util::addScript('files', 'files');
  460. \OCP\Util::addScript('files', 'filemultiselectmenu');
  461. \OCP\Util::addScript('files', 'filelist');
  462. \OCP\Util::addScript('files', 'keyboardshortcuts');
  463. \OCP\Util::addScript('files', 'operationprogressbar');
  464. }
  465. // Load Viewer scripts
  466. if (class_exists(LoadViewer::class)) {
  467. $this->eventDispatcher->dispatchTyped(new LoadViewer());
  468. }
  469. // OpenGraph Support: http://ogp.me/
  470. \OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]);
  471. \OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
  472. \OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]);
  473. \OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]);
  474. \OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]);
  475. \OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]);
  476. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
  477. $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
  478. $csp->addAllowedFrameDomain('\'self\'');
  479. $response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl);
  480. $response->setHeaderTitle($shareTmpl['filename']);
  481. if ($shareTmpl['shareOwner'] !== '') {
  482. $response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']]));
  483. }
  484. $isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== \OCP\Constants::PERMISSION_CREATE;
  485. if ($isNoneFileDropFolder && !$share->getHideDownload()) {
  486. \OCP\Util::addScript('files_sharing', 'public_note');
  487. $downloadWhite = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
  488. $downloadAllWhite = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
  489. $download = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
  490. $downloadAll = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
  491. $directLink = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']);
  492. // TRANSLATORS The placeholder refers to the software product name as in 'Add to your Nextcloud'
  493. $externalShare = new ExternalShareMenuAction($this->l10n->t('Add to your %s', [$this->defaults->getProductName()]), 'icon-external', $shareTmpl['owner'], $shareTmpl['shareOwner'], $shareTmpl['filename']);
  494. $responseComposer = [];
  495. if ($shareIsFolder) {
  496. $responseComposer[] = $downloadAllWhite;
  497. $responseComposer[] = $downloadAll;
  498. } else {
  499. $responseComposer[] = $downloadWhite;
  500. $responseComposer[] = $download;
  501. }
  502. $responseComposer[] = $directLink;
  503. if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled()) {
  504. $responseComposer[] = $externalShare;
  505. }
  506. $response->setHeaderActions($responseComposer);
  507. }
  508. $response->setContentSecurityPolicy($csp);
  509. $this->emitAccessShareHook($share);
  510. $this->emitShareAccessEvent($share, self::SHARE_ACCESS);
  511. return $response;
  512. }
  513. /**
  514. * @PublicPage
  515. * @NoCSRFRequired
  516. * @NoSameSiteCookieRequired
  517. *
  518. * @param string $token
  519. * @param string $files
  520. * @param string $path
  521. * @param string $downloadStartSecret
  522. * @return void|\OCP\AppFramework\Http\Response
  523. * @throws NotFoundException
  524. */
  525. public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
  526. \OC_User::setIncognitoMode(true);
  527. $share = $this->shareManager->getShareByToken($token);
  528. if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
  529. return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
  530. }
  531. $files_list = null;
  532. if (!is_null($files)) { // download selected files
  533. $files_list = json_decode($files);
  534. // in case we get only a single file
  535. if ($files_list === null) {
  536. $files_list = [$files];
  537. }
  538. // Just in case $files is a single int like '1234'
  539. if (!is_array($files_list)) {
  540. $files_list = [$files_list];
  541. }
  542. }
  543. if (!$this->validateShare($share)) {
  544. throw new NotFoundException();
  545. }
  546. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  547. $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
  548. // Single file share
  549. if ($share->getNode() instanceof \OCP\Files\File) {
  550. // Single file download
  551. $this->singleFileDownloaded($share, $share->getNode());
  552. }
  553. // Directory share
  554. else {
  555. /** @var \OCP\Files\Folder $node */
  556. $node = $share->getNode();
  557. // Try to get the path
  558. if ($path !== '') {
  559. try {
  560. $node = $node->get($path);
  561. } catch (NotFoundException $e) {
  562. $this->emitAccessShareHook($share, 404, 'Share not found');
  563. $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
  564. return new NotFoundResponse();
  565. }
  566. }
  567. $originalSharePath = $userFolder->getRelativePath($node->getPath());
  568. if ($node instanceof \OCP\Files\File) {
  569. // Single file download
  570. $this->singleFileDownloaded($share, $share->getNode());
  571. } else {
  572. try {
  573. if (!empty($files_list)) {
  574. $this->fileListDownloaded($share, $files_list, $node);
  575. } else {
  576. // The folder is downloaded
  577. $this->singleFileDownloaded($share, $share->getNode());
  578. }
  579. } catch (NotFoundException $e) {
  580. return new NotFoundResponse();
  581. }
  582. }
  583. }
  584. /* FIXME: We should do this all nicely in OCP */
  585. OC_Util::tearDownFS();
  586. OC_Util::setupFS($share->getShareOwner());
  587. /**
  588. * this sets a cookie to be able to recognize the start of the download
  589. * the content must not be longer than 32 characters and must only contain
  590. * alphanumeric characters
  591. */
  592. if (!empty($downloadStartSecret)
  593. && !isset($downloadStartSecret[32])
  594. && preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
  595. // FIXME: set on the response once we use an actual app framework response
  596. setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
  597. }
  598. $this->emitAccessShareHook($share);
  599. $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
  600. $server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
  601. /**
  602. * Http range requests support
  603. */
  604. if (isset($_SERVER['HTTP_RANGE'])) {
  605. $server_params['range'] = $this->request->getHeader('Range');
  606. }
  607. // download selected files
  608. if (!is_null($files) && $files !== '') {
  609. // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
  610. // after dispatching the request which results in a "Cannot modify header information" notice.
  611. OC_Files::get($originalSharePath, $files_list, $server_params);
  612. exit();
  613. } else {
  614. // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
  615. // after dispatching the request which results in a "Cannot modify header information" notice.
  616. OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
  617. exit();
  618. }
  619. }
  620. /**
  621. * create activity for every downloaded file
  622. *
  623. * @param Share\IShare $share
  624. * @param array $files_list
  625. * @param \OCP\Files\Folder $node
  626. * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
  627. */
  628. protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
  629. if ($share->getHideDownload() && count($files_list) > 1) {
  630. throw new NotFoundException('Downloading more than 1 file');
  631. }
  632. foreach ($files_list as $file) {
  633. $subNode = $node->get($file);
  634. $this->singleFileDownloaded($share, $subNode);
  635. }
  636. }
  637. /**
  638. * create activity if a single file was downloaded from a link share
  639. *
  640. * @param Share\IShare $share
  641. * @throws NotFoundException when trying to download a folder of a "hide download" share
  642. */
  643. protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
  644. if ($share->getHideDownload() && $node instanceof Folder) {
  645. throw new NotFoundException('Downloading a folder');
  646. }
  647. $fileId = $node->getId();
  648. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  649. $userNodeList = $userFolder->getById($fileId);
  650. $userNode = $userNodeList[0];
  651. $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  652. $userPath = $userFolder->getRelativePath($userNode->getPath());
  653. $ownerPath = $ownerFolder->getRelativePath($node->getPath());
  654. $remoteAddress = $this->request->getRemoteAddress();
  655. $dateTime = new \DateTime();
  656. $dateTime = $dateTime->format('Y-m-d H');
  657. $remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
  658. $parameters = [$userPath];
  659. if ($share->getShareType() === IShare::TYPE_EMAIL) {
  660. if ($node instanceof \OCP\Files\File) {
  661. $subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
  662. } else {
  663. $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
  664. }
  665. $parameters[] = $share->getSharedWith();
  666. } else {
  667. if ($node instanceof \OCP\Files\File) {
  668. $subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
  669. $parameters[] = $remoteAddressHash;
  670. } else {
  671. $subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
  672. $parameters[] = $remoteAddressHash;
  673. }
  674. }
  675. $this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
  676. if ($share->getShareOwner() !== $share->getSharedBy()) {
  677. $parameters[0] = $ownerPath;
  678. $this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
  679. }
  680. }
  681. /**
  682. * publish activity
  683. *
  684. * @param string $subject
  685. * @param array $parameters
  686. * @param string $affectedUser
  687. * @param int $fileId
  688. * @param string $filePath
  689. */
  690. protected function publishActivity($subject,
  691. array $parameters,
  692. $affectedUser,
  693. $fileId,
  694. $filePath) {
  695. $event = $this->activityManager->generateEvent();
  696. $event->setApp('files_sharing')
  697. ->setType('public_links')
  698. ->setSubject($subject, $parameters)
  699. ->setAffectedUser($affectedUser)
  700. ->setObject('files', $fileId, $filePath);
  701. $this->activityManager->publish($event);
  702. }
  703. }