You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

747 lines
26 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  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) <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 <pvince81@owncloud.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_Files;
  45. use OC_Util;
  46. use OC\Security\CSP\ContentSecurityPolicy;
  47. use OCA\FederatedFileSharing\FederatedShareProvider;
  48. use OCA\Files_Sharing\Activity\Providers\Downloads;
  49. use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
  50. use OCA\Viewer\Event\LoadViewer;
  51. use OCP\Accounts\IAccountManager;
  52. use OCP\AppFramework\AuthPublicShareController;
  53. use OCP\AppFramework\Http\NotFoundResponse;
  54. use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
  55. use OCP\AppFramework\Http\Template\LinkMenuAction;
  56. use OCP\AppFramework\Http\Template\PublicTemplateResponse;
  57. use OCP\AppFramework\Http\Template\SimpleMenuAction;
  58. use OCP\AppFramework\Http\TemplateResponse;
  59. use OCP\Defaults;
  60. use OCP\EventDispatcher\IEventDispatcher;
  61. use OCP\Files\Folder;
  62. use OCP\Files\IRootFolder;
  63. use OCP\Files\NotFoundException;
  64. use OCP\IConfig;
  65. use OCP\IL10N;
  66. use OCP\ILogger;
  67. use OCP\IPreview;
  68. use OCP\IRequest;
  69. use OCP\ISession;
  70. use OCP\IURLGenerator;
  71. use OCP\IUser;
  72. use OCP\IUserManager;
  73. use OCP\Share;
  74. use OCP\Share\Exceptions\ShareNotFound;
  75. use OCP\Share\IManager as ShareManager;
  76. use OCP\Share\IShare;
  77. use OCP\Template;
  78. /**
  79. * Class ShareController
  80. *
  81. * @package OCA\Files_Sharing\Controllers
  82. */
  83. class ShareController extends AuthPublicShareController {
  84. /** @var IConfig */
  85. protected $config;
  86. /** @var IUserManager */
  87. protected $userManager;
  88. /** @var ILogger */
  89. protected $logger;
  90. /** @var \OCP\Activity\IManager */
  91. protected $activityManager;
  92. /** @var IPreview */
  93. protected $previewManager;
  94. /** @var IRootFolder */
  95. protected $rootFolder;
  96. /** @var FederatedShareProvider */
  97. protected $federatedShareProvider;
  98. /** @var IAccountManager */
  99. protected $accountManager;
  100. /** @var IEventDispatcher */
  101. protected $eventDispatcher;
  102. /** @var IL10N */
  103. protected $l10n;
  104. /** @var Defaults */
  105. protected $defaults;
  106. /** @var ShareManager */
  107. protected $shareManager;
  108. /** @var Share\IShare */
  109. protected $share;
  110. /**
  111. * @param string $appName
  112. * @param IRequest $request
  113. * @param IConfig $config
  114. * @param IURLGenerator $urlGenerator
  115. * @param IUserManager $userManager
  116. * @param ILogger $logger
  117. * @param \OCP\Activity\IManager $activityManager
  118. * @param \OCP\Share\IManager $shareManager
  119. * @param ISession $session
  120. * @param IPreview $previewManager
  121. * @param IRootFolder $rootFolder
  122. * @param FederatedShareProvider $federatedShareProvider
  123. * @param IAccountManager $accountManager
  124. * @param IEventDispatcher $eventDispatcher
  125. * @param IL10N $l10n
  126. * @param Defaults $defaults
  127. */
  128. public function __construct(string $appName,
  129. IRequest $request,
  130. IConfig $config,
  131. IURLGenerator $urlGenerator,
  132. IUserManager $userManager,
  133. ILogger $logger,
  134. \OCP\Activity\IManager $activityManager,
  135. ShareManager $shareManager,
  136. ISession $session,
  137. IPreview $previewManager,
  138. IRootFolder $rootFolder,
  139. FederatedShareProvider $federatedShareProvider,
  140. IAccountManager $accountManager,
  141. IEventDispatcher $eventDispatcher,
  142. IL10N $l10n,
  143. Defaults $defaults) {
  144. parent::__construct($appName, $request, $session, $urlGenerator);
  145. $this->config = $config;
  146. $this->userManager = $userManager;
  147. $this->logger = $logger;
  148. $this->activityManager = $activityManager;
  149. $this->previewManager = $previewManager;
  150. $this->rootFolder = $rootFolder;
  151. $this->federatedShareProvider = $federatedShareProvider;
  152. $this->accountManager = $accountManager;
  153. $this->eventDispatcher = $eventDispatcher;
  154. $this->l10n = $l10n;
  155. $this->defaults = $defaults;
  156. $this->shareManager = $shareManager;
  157. }
  158. /**
  159. * @PublicPage
  160. * @NoCSRFRequired
  161. *
  162. * Show the authentication page
  163. * The form has to submit to the authenticate method route
  164. */
  165. public function showAuthenticate(): TemplateResponse {
  166. $templateParameters = ['share' => $this->share];
  167. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
  168. $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
  169. if ($this->share->getSendPasswordByTalk()) {
  170. $csp = new ContentSecurityPolicy();
  171. $csp->addAllowedConnectDomain('*');
  172. $csp->addAllowedMediaDomain('blob:');
  173. $response->setContentSecurityPolicy($csp);
  174. }
  175. return $response;
  176. }
  177. /**
  178. * The template to show when authentication failed
  179. */
  180. protected function showAuthFailed(): TemplateResponse {
  181. $templateParameters = ['share' => $this->share, 'wrongpw' => true];
  182. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
  183. $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
  184. if ($this->share->getSendPasswordByTalk()) {
  185. $csp = new ContentSecurityPolicy();
  186. $csp->addAllowedConnectDomain('*');
  187. $csp->addAllowedMediaDomain('blob:');
  188. $response->setContentSecurityPolicy($csp);
  189. }
  190. return $response;
  191. }
  192. protected function verifyPassword(string $password): bool {
  193. return $this->shareManager->checkPassword($this->share, $password);
  194. }
  195. protected function getPasswordHash(): string {
  196. return $this->share->getPassword();
  197. }
  198. public function isValidToken(): bool {
  199. try {
  200. $this->share = $this->shareManager->getShareByToken($this->getToken());
  201. } catch (ShareNotFound $e) {
  202. return false;
  203. }
  204. return true;
  205. }
  206. protected function isPasswordProtected(): bool {
  207. return $this->share->getPassword() !== null;
  208. }
  209. protected function authSucceeded() {
  210. // For share this was always set so it is still used in other apps
  211. $this->session->set('public_link_authenticated', (string)$this->share->getId());
  212. }
  213. protected function authFailed() {
  214. $this->emitAccessShareHook($this->share, 403, 'Wrong password');
  215. }
  216. /**
  217. * throws hooks when a share is attempted to be accessed
  218. *
  219. * @param \OCP\Share\IShare|string $share the Share instance if available,
  220. * otherwise token
  221. * @param int $errorCode
  222. * @param string $errorMessage
  223. * @throws \OC\HintException
  224. * @throws \OC\ServerNotAvailableException
  225. */
  226. protected function emitAccessShareHook($share, $errorCode = 200, $errorMessage = '') {
  227. $itemType = $itemSource = $uidOwner = '';
  228. $token = $share;
  229. $exception = null;
  230. if ($share instanceof \OCP\Share\IShare) {
  231. try {
  232. $token = $share->getToken();
  233. $uidOwner = $share->getSharedBy();
  234. $itemType = $share->getNodeType();
  235. $itemSource = $share->getNodeId();
  236. } catch (\Exception $e) {
  237. // we log what we know and pass on the exception afterwards
  238. $exception = $e;
  239. }
  240. }
  241. \OC_Hook::emit(Share::class, 'share_link_access', [
  242. 'itemType' => $itemType,
  243. 'itemSource' => $itemSource,
  244. 'uidOwner' => $uidOwner,
  245. 'token' => $token,
  246. 'errorCode' => $errorCode,
  247. 'errorMessage' => $errorMessage,
  248. ]);
  249. if (!is_null($exception)) {
  250. throw $exception;
  251. }
  252. }
  253. /**
  254. * Validate the permissions of the share
  255. *
  256. * @param Share\IShare $share
  257. * @return bool
  258. */
  259. private function validateShare(\OCP\Share\IShare $share) {
  260. // If the owner is disabled no access to the linke is granted
  261. $owner = $this->userManager->get($share->getShareOwner());
  262. if ($owner === null || !$owner->isEnabled()) {
  263. return false;
  264. }
  265. // If the initiator of the share is disabled no access is granted
  266. $initiator = $this->userManager->get($share->getSharedBy());
  267. if ($initiator === null || !$initiator->isEnabled()) {
  268. return false;
  269. }
  270. return $share->getNode()->isReadable() && $share->getNode()->isShareable();
  271. }
  272. /**
  273. * @PublicPage
  274. * @NoCSRFRequired
  275. *
  276. *
  277. * @param string $path
  278. * @return TemplateResponse
  279. * @throws NotFoundException
  280. * @throws \Exception
  281. */
  282. public function showShare($path = ''): TemplateResponse {
  283. \OC_User::setIncognitoMode(true);
  284. // Check whether share exists
  285. try {
  286. $share = $this->shareManager->getShareByToken($this->getToken());
  287. } catch (ShareNotFound $e) {
  288. $this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
  289. throw new NotFoundException();
  290. }
  291. if (!$this->validateShare($share)) {
  292. throw new NotFoundException();
  293. }
  294. $shareNode = $share->getNode();
  295. // We can't get the path of a file share
  296. try {
  297. if ($shareNode instanceof \OCP\Files\File && $path !== '') {
  298. $this->emitAccessShareHook($share, 404, 'Share not found');
  299. throw new NotFoundException();
  300. }
  301. } catch (\Exception $e) {
  302. $this->emitAccessShareHook($share, 404, 'Share not found');
  303. throw $e;
  304. }
  305. $shareTmpl = [];
  306. $shareTmpl['owner'] = '';
  307. $shareTmpl['shareOwner'] = '';
  308. $owner = $this->userManager->get($share->getShareOwner());
  309. if ($owner instanceof IUser) {
  310. $ownerAccount = $this->accountManager->getAccount($owner);
  311. $ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
  312. if ($ownerName->getScope() === IAccountManager::VISIBILITY_PUBLIC) {
  313. $shareTmpl['owner'] = $owner->getUID();
  314. $shareTmpl['shareOwner'] = $owner->getDisplayName();
  315. }
  316. }
  317. $shareTmpl['filename'] = $shareNode->getName();
  318. $shareTmpl['directory_path'] = $share->getTarget();
  319. $shareTmpl['note'] = $share->getNote();
  320. $shareTmpl['mimetype'] = $shareNode->getMimetype();
  321. $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype());
  322. $shareTmpl['dirToken'] = $this->getToken();
  323. $shareTmpl['sharingToken'] = $this->getToken();
  324. $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
  325. $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
  326. $shareTmpl['dir'] = '';
  327. $shareTmpl['nonHumanFileSize'] = $shareNode->getSize();
  328. $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($shareNode->getSize());
  329. $shareTmpl['hideDownload'] = $share->getHideDownload();
  330. $hideFileList = false;
  331. if ($shareNode instanceof \OCP\Files\Folder) {
  332. $shareIsFolder = true;
  333. try {
  334. $folderNode = $shareNode->get($path);
  335. } catch (\OCP\Files\NotFoundException $e) {
  336. $this->emitAccessShareHook($share, 404, 'Share not found');
  337. throw new NotFoundException();
  338. }
  339. $shareTmpl['dir'] = $shareNode->getRelativePath($folderNode->getPath());
  340. /*
  341. * The OC_Util methods require a view. This just uses the node API
  342. */
  343. $freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath());
  344. if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
  345. $freeSpace = max($freeSpace, 0);
  346. } else {
  347. $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
  348. }
  349. $hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ);
  350. $maxUploadFilesize = $freeSpace;
  351. $folder = new Template('files', 'list', '');
  352. $folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
  353. $folder->assign('dirToken', $this->getToken());
  354. $folder->assign('permissions', \OCP\Constants::PERMISSION_READ);
  355. $folder->assign('isPublic', true);
  356. $folder->assign('hideFileList', $hideFileList);
  357. $folder->assign('publicUploadEnabled', 'no');
  358. // default to list view
  359. $folder->assign('showgridview', false);
  360. $folder->assign('uploadMaxFilesize', $maxUploadFilesize);
  361. $folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize));
  362. $folder->assign('freeSpace', $freeSpace);
  363. $folder->assign('usedSpacePercent', 0);
  364. $folder->assign('trash', false);
  365. $shareTmpl['folder'] = $folder->fetchPage();
  366. } else {
  367. $shareIsFolder = false;
  368. }
  369. // default to list view
  370. $shareTmpl['showgridview'] = false;
  371. $shareTmpl['hideFileList'] = $hideFileList;
  372. $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $this->getToken()]);
  373. $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $this->getToken()]);
  374. $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
  375. $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
  376. $shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
  377. $shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
  378. $shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null);
  379. $shareTmpl['previewURL'] = $shareTmpl['downloadURL'];
  380. if ($shareTmpl['previewSupported']) {
  381. $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview',
  382. ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
  383. $ogPreview = $shareTmpl['previewImage'];
  384. // We just have direct previews for image files
  385. if ($shareNode->getMimePart() === 'image') {
  386. $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $this->getToken()]);
  387. $ogPreview = $shareTmpl['previewURL'];
  388. //Whatapp is kind of picky about their size requirements
  389. if ($this->request->isUserAgent(['/^WhatsApp/'])) {
  390. $ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
  391. 'token' => $this->getToken(),
  392. 'x' => 256,
  393. 'y' => 256,
  394. 'a' => true,
  395. ]);
  396. }
  397. }
  398. } else {
  399. $shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
  400. $ogPreview = $shareTmpl['previewImage'];
  401. }
  402. // Load files we need
  403. \OCP\Util::addScript('files', 'semaphore');
  404. \OCP\Util::addScript('files', 'file-upload');
  405. \OCP\Util::addStyle('files_sharing', 'publicView');
  406. \OCP\Util::addScript('files_sharing', 'public');
  407. \OCP\Util::addScript('files_sharing', 'templates');
  408. \OCP\Util::addScript('files', 'fileactions');
  409. \OCP\Util::addScript('files', 'fileactionsmenu');
  410. \OCP\Util::addScript('files', 'jquery.fileupload');
  411. \OCP\Util::addScript('files_sharing', 'files_drop');
  412. if (isset($shareTmpl['folder'])) {
  413. // JS required for folders
  414. \OCP\Util::addStyle('files', 'merged');
  415. \OCP\Util::addScript('files', 'filesummary');
  416. \OCP\Util::addScript('files', 'templates');
  417. \OCP\Util::addScript('files', 'breadcrumb');
  418. \OCP\Util::addScript('files', 'fileinfomodel');
  419. \OCP\Util::addScript('files', 'newfilemenu');
  420. \OCP\Util::addScript('files', 'files');
  421. \OCP\Util::addScript('files', 'filemultiselectmenu');
  422. \OCP\Util::addScript('files', 'filelist');
  423. \OCP\Util::addScript('files', 'keyboardshortcuts');
  424. \OCP\Util::addScript('files', 'operationprogressbar');
  425. // Load Viewer scripts
  426. if (class_exists(LoadViewer::class)) {
  427. $this->eventDispatcher->dispatchTyped(new LoadViewer());
  428. }
  429. }
  430. // OpenGraph Support: http://ogp.me/
  431. \OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]);
  432. \OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
  433. \OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]);
  434. \OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]);
  435. \OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]);
  436. \OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]);
  437. $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
  438. $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
  439. $csp->addAllowedFrameDomain('\'self\'');
  440. $response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl);
  441. $response->setHeaderTitle($shareTmpl['filename']);
  442. if ($shareTmpl['shareOwner'] !== '') {
  443. $response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']]));
  444. }
  445. $isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== \OCP\Constants::PERMISSION_CREATE;
  446. if ($isNoneFileDropFolder && !$share->getHideDownload()) {
  447. \OCP\Util::addScript('files_sharing', 'public_note');
  448. $downloadWhite = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
  449. $downloadAllWhite = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
  450. $download = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
  451. $downloadAll = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
  452. $directLink = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']);
  453. $externalShare = new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['shareOwner'], $shareTmpl['filename']);
  454. $responseComposer = [];
  455. if ($shareIsFolder) {
  456. $responseComposer[] = $downloadAllWhite;
  457. $responseComposer[] = $downloadAll;
  458. } else {
  459. $responseComposer[] = $downloadWhite;
  460. $responseComposer[] = $download;
  461. }
  462. $responseComposer[] = $directLink;
  463. if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled()) {
  464. $responseComposer[] = $externalShare;
  465. }
  466. $response->setHeaderActions($responseComposer);
  467. }
  468. $response->setContentSecurityPolicy($csp);
  469. $this->emitAccessShareHook($share);
  470. return $response;
  471. }
  472. /**
  473. * @PublicPage
  474. * @NoCSRFRequired
  475. * @NoSameSiteCookieRequired
  476. *
  477. * @param string $token
  478. * @param string $files
  479. * @param string $path
  480. * @param string $downloadStartSecret
  481. * @return void|\OCP\AppFramework\Http\Response
  482. * @throws NotFoundException
  483. */
  484. public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
  485. \OC_User::setIncognitoMode(true);
  486. $share = $this->shareManager->getShareByToken($token);
  487. if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
  488. return new \OCP\AppFramework\Http\DataResponse('Share is read-only');
  489. }
  490. $files_list = null;
  491. if (!is_null($files)) { // download selected files
  492. $files_list = json_decode($files);
  493. // in case we get only a single file
  494. if ($files_list === null) {
  495. $files_list = [$files];
  496. }
  497. // Just in case $files is a single int like '1234'
  498. if (!is_array($files_list)) {
  499. $files_list = [$files_list];
  500. }
  501. }
  502. if (!$this->validateShare($share)) {
  503. throw new NotFoundException();
  504. }
  505. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  506. $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
  507. // Single file share
  508. if ($share->getNode() instanceof \OCP\Files\File) {
  509. // Single file download
  510. $this->singleFileDownloaded($share, $share->getNode());
  511. }
  512. // Directory share
  513. else {
  514. /** @var \OCP\Files\Folder $node */
  515. $node = $share->getNode();
  516. // Try to get the path
  517. if ($path !== '') {
  518. try {
  519. $node = $node->get($path);
  520. } catch (NotFoundException $e) {
  521. $this->emitAccessShareHook($share, 404, 'Share not found');
  522. return new NotFoundResponse();
  523. }
  524. }
  525. $originalSharePath = $userFolder->getRelativePath($node->getPath());
  526. if ($node instanceof \OCP\Files\File) {
  527. // Single file download
  528. $this->singleFileDownloaded($share, $share->getNode());
  529. } else {
  530. try {
  531. if (!empty($files_list)) {
  532. $this->fileListDownloaded($share, $files_list, $node);
  533. } else {
  534. // The folder is downloaded
  535. $this->singleFileDownloaded($share, $share->getNode());
  536. }
  537. } catch (NotFoundException $e) {
  538. return new NotFoundResponse();
  539. }
  540. }
  541. }
  542. /* FIXME: We should do this all nicely in OCP */
  543. OC_Util::tearDownFS();
  544. OC_Util::setupFS($share->getShareOwner());
  545. /**
  546. * this sets a cookie to be able to recognize the start of the download
  547. * the content must not be longer than 32 characters and must only contain
  548. * alphanumeric characters
  549. */
  550. if (!empty($downloadStartSecret)
  551. && !isset($downloadStartSecret[32])
  552. && preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
  553. // FIXME: set on the response once we use an actual app framework response
  554. setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
  555. }
  556. $this->emitAccessShareHook($share);
  557. $server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
  558. /**
  559. * Http range requests support
  560. */
  561. if (isset($_SERVER['HTTP_RANGE'])) {
  562. $server_params['range'] = $this->request->getHeader('Range');
  563. }
  564. // download selected files
  565. if (!is_null($files) && $files !== '') {
  566. // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
  567. // after dispatching the request which results in a "Cannot modify header information" notice.
  568. OC_Files::get($originalSharePath, $files_list, $server_params);
  569. exit();
  570. } else {
  571. // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
  572. // after dispatching the request which results in a "Cannot modify header information" notice.
  573. OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
  574. exit();
  575. }
  576. }
  577. /**
  578. * create activity for every downloaded file
  579. *
  580. * @param Share\IShare $share
  581. * @param array $files_list
  582. * @param \OCP\Files\Folder $node
  583. * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
  584. */
  585. protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
  586. if ($share->getHideDownload() && count($files_list) > 1) {
  587. throw new NotFoundException('Downloading more than 1 file');
  588. }
  589. foreach ($files_list as $file) {
  590. $subNode = $node->get($file);
  591. $this->singleFileDownloaded($share, $subNode);
  592. }
  593. }
  594. /**
  595. * create activity if a single file was downloaded from a link share
  596. *
  597. * @param Share\IShare $share
  598. * @throws NotFoundException when trying to download a folder of a "hide download" share
  599. */
  600. protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
  601. if ($share->getHideDownload() && $node instanceof Folder) {
  602. throw new NotFoundException('Downloading a folder');
  603. }
  604. $fileId = $node->getId();
  605. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  606. $userNodeList = $userFolder->getById($fileId);
  607. $userNode = $userNodeList[0];
  608. $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  609. $userPath = $userFolder->getRelativePath($userNode->getPath());
  610. $ownerPath = $ownerFolder->getRelativePath($node->getPath());
  611. $parameters = [$userPath];
  612. if ($share->getShareType() === IShare::TYPE_EMAIL) {
  613. if ($node instanceof \OCP\Files\File) {
  614. $subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
  615. } else {
  616. $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
  617. }
  618. $parameters[] = $share->getSharedWith();
  619. } else {
  620. if ($node instanceof \OCP\Files\File) {
  621. $subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
  622. } else {
  623. $subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
  624. }
  625. }
  626. $this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
  627. if ($share->getShareOwner() !== $share->getSharedBy()) {
  628. $parameters[0] = $ownerPath;
  629. $this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
  630. }
  631. }
  632. /**
  633. * publish activity
  634. *
  635. * @param string $subject
  636. * @param array $parameters
  637. * @param string $affectedUser
  638. * @param int $fileId
  639. * @param string $filePath
  640. */
  641. protected function publishActivity($subject,
  642. array $parameters,
  643. $affectedUser,
  644. $fileId,
  645. $filePath) {
  646. $event = $this->activityManager->generateEvent();
  647. $event->setApp('files_sharing')
  648. ->setType('public_links')
  649. ->setSubject($subject, $parameters)
  650. ->setAffectedUser($affectedUser)
  651. ->setObject('files', $fileId, $filePath);
  652. $this->activityManager->publish($event);
  653. }
  654. }