Passed
Push — master ( 61a02b...010b07 )
by Roeland
19:08 queued 08:59
created

ShareController::singleFileDownloaded()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 36
rs 8.5866
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Daniel Calviño Sánchez <[email protected]>
9
 * @author Georg Ehrke <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author John Molakvoæ (skjnldsv) <[email protected]>
12
 * @author Jonas Sulzer <[email protected]>
13
 * @author Julius Härtl <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author MartB <[email protected]>
16
 * @author Maxence Lange <[email protected]>
17
 * @author Michael Weimann <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Piotr Filiciak <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Sascha Sambale <[email protected]>
23
 * @author Thomas Müller <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 *
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program. If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
namespace OCA\Files_Sharing\Controller;
43
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\Viewer\Event\LoadViewer;
0 ignored issues
show
Bug introduced by
The type OCA\Viewer\Event\LoadViewer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
use OCP\AppFramework\AuthPublicShareController;
51
use OCP\AppFramework\Http\NotFoundResponse;
52
use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
53
use OCP\AppFramework\Http\Template\LinkMenuAction;
54
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
55
use OCP\AppFramework\Http\Template\SimpleMenuAction;
56
use OCP\AppFramework\Http\TemplateResponse;
57
use OCP\Defaults;
58
use OCP\Files\Folder;
59
use OCP\Files\IRootFolder;
60
use OCP\Files\NotFoundException;
61
use OCP\IConfig;
62
use OCP\IL10N;
63
use OCP\ILogger;
64
use OCP\IPreview;
65
use OCP\IRequest;
66
use OCP\ISession;
67
use OCP\IURLGenerator;
68
use OCP\IUserManager;
69
use OCP\Share;
70
use OCP\Share\Exceptions\ShareNotFound;
71
use OCP\Share\IManager as ShareManager;
72
use OCP\Template;
73
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
74
use Symfony\Component\EventDispatcher\GenericEvent;
75
76
/**
77
 * Class ShareController
78
 *
79
 * @package OCA\Files_Sharing\Controllers
80
 */
81
class ShareController extends AuthPublicShareController {
82
83
	/** @var IConfig */
84
	protected $config;
85
	/** @var IUserManager */
86
	protected $userManager;
87
	/** @var ILogger */
88
	protected $logger;
89
	/** @var \OCP\Activity\IManager */
90
	protected $activityManager;
91
	/** @var IPreview */
92
	protected $previewManager;
93
	/** @var IRootFolder */
94
	protected $rootFolder;
95
	/** @var FederatedShareProvider */
96
	protected $federatedShareProvider;
97
	/** @var EventDispatcherInterface */
98
	protected $eventDispatcher;
99
	/** @var IL10N */
100
	protected $l10n;
101
	/** @var Defaults */
102
	protected $defaults;
103
	/** @var ShareManager */
104
	protected $shareManager;
105
106
	/** @var Share\IShare */
107
	protected $share;
108
109
	/**
110
	 * @param string $appName
111
	 * @param IRequest $request
112
	 * @param IConfig $config
113
	 * @param IURLGenerator $urlGenerator
114
	 * @param IUserManager $userManager
115
	 * @param ILogger $logger
116
	 * @param \OCP\Activity\IManager $activityManager
117
	 * @param \OCP\Share\IManager $shareManager
118
	 * @param ISession $session
119
	 * @param IPreview $previewManager
120
	 * @param IRootFolder $rootFolder
121
	 * @param FederatedShareProvider $federatedShareProvider
122
	 * @param EventDispatcherInterface $eventDispatcher
123
	 * @param IL10N $l10n
124
	 * @param Defaults $defaults
125
	 */
126
	public function __construct(string $appName,
127
								IRequest $request,
128
								IConfig $config,
129
								IURLGenerator $urlGenerator,
130
								IUserManager $userManager,
131
								ILogger $logger,
132
								\OCP\Activity\IManager $activityManager,
133
								ShareManager $shareManager,
134
								ISession $session,
135
								IPreview $previewManager,
136
								IRootFolder $rootFolder,
137
								FederatedShareProvider $federatedShareProvider,
138
								EventDispatcherInterface $eventDispatcher,
139
								IL10N $l10n,
140
								Defaults $defaults) {
141
		parent::__construct($appName, $request, $session, $urlGenerator);
142
143
		$this->config = $config;
144
		$this->userManager = $userManager;
145
		$this->logger = $logger;
146
		$this->activityManager = $activityManager;
147
		$this->previewManager = $previewManager;
148
		$this->rootFolder = $rootFolder;
149
		$this->federatedShareProvider = $federatedShareProvider;
150
		$this->eventDispatcher = $eventDispatcher;
151
		$this->l10n = $l10n;
152
		$this->defaults = $defaults;
153
		$this->shareManager = $shareManager;
154
	}
155
156
	/**
157
	 * @PublicPage
158
	 * @NoCSRFRequired
159
	 *
160
	 * Show the authentication page
161
	 * The form has to submit to the authenticate method route
162
	 */
163
	public function showAuthenticate(): TemplateResponse {
164
		$templateParameters = ['share' => $this->share];
165
166
		$event = new GenericEvent(null, $templateParameters);
167
		$this->eventDispatcher->dispatch('OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);
0 ignored issues
show
Bug introduced by
'OCA\Files_Sharing::load...ripts::publicShareAuth' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
		$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);
Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

167
		$this->eventDispatcher->/** @scrutinizer ignore-call */ 
168
                          dispatch('OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
168
169
		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
170
		if ($this->share->getSendPasswordByTalk()) {
171
			$csp = new ContentSecurityPolicy();
172
			$csp->addAllowedConnectDomain('*');
173
			$csp->addAllowedMediaDomain('blob:');
174
			$response->setContentSecurityPolicy($csp);
175
		}
176
177
		return $response;
178
	}
179
180
	/**
181
	 * The template to show when authentication failed
182
	 */
183
	protected function showAuthFailed(): TemplateResponse {
184
		$templateParameters = ['share' => $this->share, 'wrongpw' => true];
185
186
		$event = new GenericEvent(null, $templateParameters);
187
		$this->eventDispatcher->dispatch('OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
		$this->eventDispatcher->/** @scrutinizer ignore-call */ 
188
                          dispatch('OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'OCA\Files_Sharing::load...ripts::publicShareAuth' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
		$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCA\Files_Sharing::loadAdditionalScripts::publicShareAuth', $event);
Loading history...
188
189
		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
190
		if ($this->share->getSendPasswordByTalk()) {
191
			$csp = new ContentSecurityPolicy();
192
			$csp->addAllowedConnectDomain('*');
193
			$csp->addAllowedMediaDomain('blob:');
194
			$response->setContentSecurityPolicy($csp);
195
		}
196
197
		return $response;
198
	}
199
200
	protected function verifyPassword(string $password): bool {
201
		return $this->shareManager->checkPassword($this->share, $password);
202
	}
203
204
	protected function getPasswordHash(): string {
205
		return $this->share->getPassword();
206
	}
207
208
	public function isValidToken(): bool {
209
		try {
210
			$this->share = $this->shareManager->getShareByToken($this->getToken());
211
		} catch (ShareNotFound $e) {
212
			return false;
213
		}
214
215
		return true;
216
	}
217
218
	protected function isPasswordProtected(): bool {
219
		return $this->share->getPassword() !== null;
220
	}
221
222
	protected function authSucceeded() {
223
		// For share this was always set so it is still used in other apps
224
		$this->session->set('public_link_authenticated', (string)$this->share->getId());
225
	}
226
227
	protected function authFailed() {
228
		$this->emitAccessShareHook($this->share, 403, 'Wrong password');
229
	}
230
231
	/**
232
	 * throws hooks when a share is attempted to be accessed
233
	 *
234
	 * @param \OCP\Share\IShare|string $share the Share instance if available,
235
	 * otherwise token
236
	 * @param int $errorCode
237
	 * @param string $errorMessage
238
	 * @throws \OC\HintException
239
	 * @throws \OC\ServerNotAvailableException
240
	 */
241
	protected function emitAccessShareHook($share, $errorCode = 200, $errorMessage = '') {
242
		$itemType = $itemSource = $uidOwner = '';
243
		$token = $share;
244
		$exception = null;
245
		if($share instanceof \OCP\Share\IShare) {
246
			try {
247
				$token = $share->getToken();
248
				$uidOwner = $share->getSharedBy();
249
				$itemType = $share->getNodeType();
250
				$itemSource = $share->getNodeId();
251
			} catch (\Exception $e) {
252
				// we log what we know and pass on the exception afterwards
253
				$exception = $e;
254
			}
255
		}
256
		\OC_Hook::emit(Share::class, 'share_link_access', [
257
			'itemType' => $itemType,
258
			'itemSource' => $itemSource,
259
			'uidOwner' => $uidOwner,
260
			'token' => $token,
261
			'errorCode' => $errorCode,
262
			'errorMessage' => $errorMessage,
263
		]);
264
		if(!is_null($exception)) {
265
			throw $exception;
266
		}
267
	}
268
269
	/**
270
	 * Validate the permissions of the share
271
	 *
272
	 * @param Share\IShare $share
273
	 * @return bool
274
	 */
275
	private function validateShare(\OCP\Share\IShare $share) {
276
		// If the owner is disabled no access to the linke is granted
277
		$owner = $this->userManager->get($share->getShareOwner());
278
		if ($owner === null || !$owner->isEnabled()) {
279
			return false;
280
		}
281
282
		// If the initiator of the share is disabled no access is granted
283
		$initiator = $this->userManager->get($share->getSharedBy());
284
		if ($initiator === null || !$initiator->isEnabled()) {
285
			return false;
286
		}
287
288
		return $share->getNode()->isReadable() && $share->getNode()->isShareable();
289
	}
290
291
	/**
292
	 * @PublicPage
293
	 * @NoCSRFRequired
294
	 *
295
296
	 * @param string $path
297
	 * @return TemplateResponse
298
	 * @throws NotFoundException
299
	 * @throws \Exception
300
	 */
301
	public function showShare($path = ''): TemplateResponse {
302
		\OC_User::setIncognitoMode(true);
303
304
		// Check whether share exists
305
		try {
306
			$share = $this->shareManager->getShareByToken($this->getToken());
307
		} catch (ShareNotFound $e) {
308
			$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
309
			throw new NotFoundException();
310
		}
311
312
		if (!$this->validateShare($share)) {
313
			throw new NotFoundException();
314
		}
315
316
		$shareNode = $share->getNode();
317
318
		// We can't get the path of a file share
319
		try {
320
			if ($shareNode instanceof \OCP\Files\File && $path !== '') {
321
				$this->emitAccessShareHook($share, 404, 'Share not found');
322
				throw new NotFoundException();
323
			}
324
		} catch (\Exception $e) {
325
			$this->emitAccessShareHook($share, 404, 'Share not found');
326
			throw $e;
327
		}
328
329
		$shareTmpl = [];
330
		$shareTmpl['displayName'] = $this->userManager->get($share->getShareOwner())->getDisplayName();
331
		$shareTmpl['owner'] = $share->getShareOwner();
332
		$shareTmpl['filename'] = $shareNode->getName();
333
		$shareTmpl['directory_path'] = $share->getTarget();
334
		$shareTmpl['note'] = $share->getNote();
335
		$shareTmpl['mimetype'] = $shareNode->getMimetype();
336
		$shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype());
337
		$shareTmpl['dirToken'] = $this->getToken();
338
		$shareTmpl['sharingToken'] = $this->getToken();
339
		$shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
340
		$shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
341
		$shareTmpl['dir'] = '';
342
		$shareTmpl['nonHumanFileSize'] = $shareNode->getSize();
343
		$shareTmpl['fileSize'] = \OCP\Util::humanFileSize($shareNode->getSize());
344
		$shareTmpl['hideDownload'] = $share->getHideDownload();
345
346
		$hideFileList = false;
347
348
		if ($shareNode instanceof \OCP\Files\Folder) {
349
350
			$shareIsFolder = true;
351
352
			try {
353
				$folderNode = $shareNode->get($path);
354
			} catch (\OCP\Files\NotFoundException $e) {
355
				$this->emitAccessShareHook($share, 404, 'Share not found');
356
				throw new NotFoundException();
357
			}
358
359
			$shareTmpl['dir'] = $shareNode->getRelativePath($folderNode->getPath());
360
361
			/*
362
			 * The OC_Util methods require a view. This just uses the node API
363
			 */
364
			$freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath());
365
			if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
366
				$freeSpace = max($freeSpace, 0);
367
			} else {
368
				$freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
369
			}
370
371
			$hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ);
372
			$maxUploadFilesize = $freeSpace;
373
374
			$folder = new Template('files', 'list', '');
375
376
			$folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
377
			$folder->assign('dirToken', $this->getToken());
378
			$folder->assign('permissions', \OCP\Constants::PERMISSION_READ);
379
			$folder->assign('isPublic', true);
380
			$folder->assign('hideFileList', $hideFileList);
381
			$folder->assign('publicUploadEnabled', 'no');
382
			// default to list view
383
			$folder->assign('showgridview', false);
384
			$folder->assign('uploadMaxFilesize', $maxUploadFilesize);
385
			$folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize));
386
			$folder->assign('freeSpace', $freeSpace);
387
			$folder->assign('usedSpacePercent', 0);
388
			$folder->assign('trash', false);
389
			$shareTmpl['folder'] = $folder->fetchPage();
390
		} else {
391
			$shareIsFolder = false;
392
		}
393
394
		// default to list view
395
		$shareTmpl['showgridview'] = false;
396
397
		$shareTmpl['hideFileList'] = $hideFileList;
398
		$shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName();
399
		$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $this->getToken()]);
400
		$shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $this->getToken()]);
401
		$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
402
		$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
403
		$shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
404
		$shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
405
		$shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null);
406
		$shareTmpl['previewURL'] = $shareTmpl['downloadURL'];
407
408
		if ($shareTmpl['previewSupported']) {
409
			$shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview',
410
				['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
411
			$ogPreview = $shareTmpl['previewImage'];
412
413
			// We just have direct previews for image files
414
			if ($shareNode->getMimePart() === 'image') {
415
				$shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $this->getToken()]);
416
417
				$ogPreview = $shareTmpl['previewURL'];
418
419
				//Whatapp is kind of picky about their size requirements
420
				if ($this->request->isUserAgent(['/^WhatsApp/'])) {
421
					$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
422
						'token' => $this->getToken(),
423
						'x' => 256,
424
						'y' => 256,
425
						'a' => true,
426
					]);
427
				}
428
			}
429
		} else {
430
			$shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
431
			$ogPreview = $shareTmpl['previewImage'];
432
		}
433
434
		// Load files we need
435
		\OCP\Util::addScript('files', 'semaphore');
436
		\OCP\Util::addScript('files', 'file-upload');
437
		\OCP\Util::addStyle('files_sharing', 'publicView');
438
		\OCP\Util::addScript('files_sharing', 'public');
439
		\OCP\Util::addScript('files_sharing', 'templates');
440
		\OCP\Util::addScript('files', 'fileactions');
441
		\OCP\Util::addScript('files', 'fileactionsmenu');
442
		\OCP\Util::addScript('files', 'jquery.fileupload');
443
		\OCP\Util::addScript('files_sharing', 'files_drop');
444
445
		if (isset($shareTmpl['folder'])) {
446
			// JS required for folders
447
			\OCP\Util::addStyle('files', 'merged');
448
			\OCP\Util::addScript('files', 'filesummary');
449
			\OCP\Util::addScript('files', 'templates');
450
			\OCP\Util::addScript('files', 'breadcrumb');
451
			\OCP\Util::addScript('files', 'fileinfomodel');
452
			\OCP\Util::addScript('files', 'newfilemenu');
453
			\OCP\Util::addScript('files', 'files');
454
			\OCP\Util::addScript('files', 'filemultiselectmenu');
455
			\OCP\Util::addScript('files', 'filelist');
456
			\OCP\Util::addScript('files', 'keyboardshortcuts');
457
			\OCP\Util::addScript('files', 'operationprogressbar');
458
459
			// Load Viewer scripts
460
			if (class_exists(LoadViewer::class)) {
461
				$this->eventDispatcher->dispatch(LoadViewer::class, new LoadViewer());
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new OCA\Viewer\Event\LoadViewer(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

461
				$this->eventDispatcher->/** @scrutinizer ignore-call */ 
462
                            dispatch(LoadViewer::class, new LoadViewer());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCA\Viewer\Event\LoadViewer::class of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

461
				$this->eventDispatcher->dispatch(/** @scrutinizer ignore-type */ LoadViewer::class, new LoadViewer());
Loading history...
462
			}
463
		}
464
465
		// OpenGraph Support: http://ogp.me/
466
		\OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]);
467
		\OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
468
		\OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]);
469
		\OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]);
470
		\OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]);
471
		\OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]);
472
473
		$event = new GenericEvent(null, ['share' => $share]);
474
		$this->eventDispatcher->dispatch('OCA\Files_Sharing::loadAdditionalScripts', $event);
475
476
		$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
477
		$csp->addAllowedFrameDomain('\'self\'');
478
479
		$response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl);
480
		$response->setHeaderTitle($shareTmpl['filename']);
481
		$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']]));
482
483
		$isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== \OCP\Constants::PERMISSION_CREATE;
484
485
		if ($isNoneFileDropFolder && !$share->getHideDownload()) {
486
			\OCP\Util::addScript('files_sharing', 'public_note');
487
488
			$downloadWhite = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
489
			$downloadAllWhite = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download-white', $shareTmpl['downloadURL'], 0);
490
			$download = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
491
			$downloadAll = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']);
492
			$directLink = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']);
493
			$externalShare = new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']);
494
495
			$responseComposer = [];
496
497
			if ($shareIsFolder) {
498
				$responseComposer[] = $downloadAllWhite;
499
				$responseComposer[] = $downloadAll;
500
			} else {
501
				$responseComposer[] = $downloadWhite;
502
				$responseComposer[] = $download;
503
			}
504
			$responseComposer[] = $directLink;
505
			if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled()) {
506
				$responseComposer[] = $externalShare;
507
			}
508
509
			$response->setHeaderActions($responseComposer);
510
		}
511
512
		$response->setContentSecurityPolicy($csp);
513
514
		$this->emitAccessShareHook($share);
515
516
		return $response;
517
	}
518
519
	/**
520
	 * @PublicPage
521
	 * @NoCSRFRequired
522
	 *
523
	 * @param string $token
524
	 * @param string $files
525
	 * @param string $path
526
	 * @param string $downloadStartSecret
527
	 * @return void|\OCP\AppFramework\Http\Response
528
	 * @throws NotFoundException
529
	 */
530
	public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
531
		\OC_User::setIncognitoMode(true);
532
533
		$share = $this->shareManager->getShareByToken($token);
534
535
		if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
536
			return new \OCP\AppFramework\Http\DataResponse('Share is read-only');
0 ignored issues
show
Bug introduced by
'Share is read-only' of type string is incompatible with the type array|object expected by parameter $data of OCP\AppFramework\Http\DataResponse::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

536
			return new \OCP\AppFramework\Http\DataResponse(/** @scrutinizer ignore-type */ 'Share is read-only');
Loading history...
537
		}
538
539
		$files_list = null;
540
		if (!is_null($files)) { // download selected files
541
			$files_list = json_decode($files);
542
			// in case we get only a single file
543
			if ($files_list === null) {
544
				$files_list = [$files];
545
			}
546
			// Just in case $files is a single int like '1234'
547
			if (!is_array($files_list)) {
548
				$files_list = [$files_list];
549
			}
550
		}
551
552
		if (!$this->validateShare($share)) {
553
			throw new NotFoundException();
554
		}
555
556
		$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
557
		$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
558
559
560
		// Single file share
561
		if ($share->getNode() instanceof \OCP\Files\File) {
562
			// Single file download
563
			$this->singleFileDownloaded($share, $share->getNode());
564
		}
565
		// Directory share
566
		else {
567
			/** @var \OCP\Files\Folder $node */
568
			$node = $share->getNode();
569
570
			// Try to get the path
571
			if ($path !== '') {
572
				try {
573
					$node = $node->get($path);
574
				} catch (NotFoundException $e) {
575
					$this->emitAccessShareHook($share, 404, 'Share not found');
576
					return new NotFoundResponse();
577
				}
578
			}
579
580
			$originalSharePath = $userFolder->getRelativePath($node->getPath());
581
582
			if ($node instanceof \OCP\Files\File) {
583
				// Single file download
584
				$this->singleFileDownloaded($share, $share->getNode());
585
			} else {
586
				try {
587
					if (!empty($files_list)) {
588
						$this->fileListDownloaded($share, $files_list, $node);
589
					} else {
590
						// The folder is downloaded
591
						$this->singleFileDownloaded($share, $share->getNode());
592
					}
593
				} catch (NotFoundException $e) {
594
					return new NotFoundResponse();
595
				}
596
			}
597
		}
598
599
		/* FIXME: We should do this all nicely in OCP */
600
		OC_Util::tearDownFS();
601
		OC_Util::setupFS($share->getShareOwner());
602
603
		/**
604
		 * this sets a cookie to be able to recognize the start of the download
605
		 * the content must not be longer than 32 characters and must only contain
606
		 * alphanumeric characters
607
		 */
608
		if (!empty($downloadStartSecret)
609
			&& !isset($downloadStartSecret[32])
610
			&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
611
612
			// FIXME: set on the response once we use an actual app framework response
613
			setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
614
		}
615
616
		$this->emitAccessShareHook($share);
617
618
		$server_params = array( 'head' => $this->request->getMethod() === 'HEAD' );
619
620
		/**
621
		 * Http range requests support
622
		 */
623
		if (isset($_SERVER['HTTP_RANGE'])) {
624
			$server_params['range'] = $this->request->getHeader('Range');
625
		}
626
627
		// download selected files
628
		if (!is_null($files) && $files !== '') {
629
			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
630
			// after dispatching the request which results in a "Cannot modify header information" notice.
631
			OC_Files::get($originalSharePath, $files_list, $server_params);
632
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
633
		} else {
634
			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
635
			// after dispatching the request which results in a "Cannot modify header information" notice.
636
			OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
637
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
638
		}
639
	}
640
641
	/**
642
	 * create activity for every downloaded file
643
	 *
644
	 * @param Share\IShare $share
645
	 * @param array $files_list
646
	 * @param \OCP\Files\Folder $node
647
	 * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
648
	 */
649
	protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
650
		if ($share->getHideDownload() && count($files_list) > 1) {
651
			throw new NotFoundException('Downloading more than 1 file');
652
		}
653
654
		foreach ($files_list as $file) {
655
			$subNode = $node->get($file);
656
			$this->singleFileDownloaded($share, $subNode);
657
		}
658
659
	}
660
661
	/**
662
	 * create activity if a single file was downloaded from a link share
663
	 *
664
	 * @param Share\IShare $share
665
	 * @throws NotFoundException when trying to download a folder of a "hide download" share
666
	 */
667
	protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
668
		if ($share->getHideDownload() && $node instanceof Folder) {
669
			throw new NotFoundException('Downloading a folder');
670
		}
671
672
		$fileId = $node->getId();
673
674
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
675
		$userNodeList = $userFolder->getById($fileId);
676
		$userNode = $userNodeList[0];
677
		$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
678
		$userPath = $userFolder->getRelativePath($userNode->getPath());
679
		$ownerPath = $ownerFolder->getRelativePath($node->getPath());
680
681
		$parameters = [$userPath];
682
683
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_EMAIL has been deprecated: 17.0.0 - use IShare::TYPE_EMAIL instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

683
		if ($share->getShareType() === /** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_EMAIL) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
684
			if ($node instanceof \OCP\Files\File) {
685
				$subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
686
			} else {
687
				$subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
688
			}
689
			$parameters[] = $share->getSharedWith();
690
		} else {
691
			if ($node instanceof \OCP\Files\File) {
692
				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
693
			} else {
694
				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
695
			}
696
		}
697
698
		$this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
699
700
		if ($share->getShareOwner() !== $share->getSharedBy()) {
701
			$parameters[0] = $ownerPath;
702
			$this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
703
		}
704
	}
705
706
	/**
707
	 * publish activity
708
	 *
709
	 * @param string $subject
710
	 * @param array $parameters
711
	 * @param string $affectedUser
712
	 * @param int $fileId
713
	 * @param string $filePath
714
	 */
715
	protected function publishActivity($subject,
716
										array $parameters,
717
										$affectedUser,
718
										$fileId,
719
										$filePath) {
720
721
		$event = $this->activityManager->generateEvent();
722
		$event->setApp('files_sharing')
723
			->setType('public_links')
724
			->setSubject($subject, $parameters)
725
			->setAffectedUser($affectedUser)
726
			->setObject('files', $fileId, $filePath);
727
		$this->activityManager->publish($event);
728
	}
729
730
731
}
732