FilesPlugin::handleGetProperties()   F
last analyzed

Complexity

Conditions 30
Paths 66

Size

Total Lines 223
Code Lines 120

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 120
nc 66
nop 2
dl 0
loc 223
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Michael Jobst <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Tobias Kaminsky <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
namespace OCA\DAV\Connector\Sabre;
35
36
use OC\AppFramework\Http\Request;
37
use OC\Metadata\IMetadataManager;
38
use OCP\Constants;
39
use OCP\Files\ForbiddenException;
40
use OCP\Files\StorageNotAvailableException;
41
use OCP\IConfig;
42
use OCP\IPreview;
43
use OCP\IRequest;
44
use OCP\IUserSession;
45
use Psr\Log\LoggerInterface;
46
use Sabre\DAV\Exception\Forbidden;
47
use Sabre\DAV\Exception\NotFound;
48
use Sabre\DAV\IFile;
49
use Sabre\DAV\PropFind;
50
use Sabre\DAV\PropPatch;
51
use Sabre\DAV\ServerPlugin;
52
use Sabre\DAV\Server;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OCA\DAV\Connector\Sabre\Server. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
53
use Sabre\DAV\Tree;
54
use Sabre\HTTP\RequestInterface;
55
use Sabre\HTTP\ResponseInterface;
56
57
class FilesPlugin extends ServerPlugin {
58
	// namespace
59
	public const NS_OWNCLOUD = 'http://owncloud.org/ns';
60
	public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
61
	public const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
62
	public const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
63
	public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
64
	public const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
65
	public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
66
	public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
67
	public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
68
	public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
69
	public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
70
	public const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
71
	public const CREATIONDATE_PROPERTYNAME = '{DAV:}creationdate';
72
	public const DISPLAYNAME_PROPERTYNAME = '{DAV:}displayname';
73
	public const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
74
	public const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
75
	public const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
76
	public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
77
	public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
78
	public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
79
	public const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
80
	public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
81
	public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
82
	public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
83
	public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
84
	public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
85
	public const SUBFILE_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-file-count';
86
	public const FILE_METADATA_SIZE = '{http://nextcloud.org/ns}file-metadata-size';
87
88
	/** Reference to main server object */
89
	private ?Server $server = null;
90
	private Tree $tree;
91
	private IUserSession $userSession;
92
93
	/**
94
	 * Whether this is public webdav.
95
	 * If true, some returned information will be stripped off.
96
	 */
97
	private bool $isPublic;
98
	private bool $downloadAttachment;
99
	private IConfig $config;
100
	private IRequest $request;
101
	private IPreview $previewManager;
102
103
	public function __construct(Tree $tree,
104
								IConfig $config,
105
								IRequest $request,
106
								IPreview $previewManager,
107
								IUserSession $userSession,
108
								bool $isPublic = false,
109
								bool $downloadAttachment = true) {
110
		$this->tree = $tree;
111
		$this->config = $config;
112
		$this->request = $request;
113
		$this->userSession = $userSession;
114
		$this->isPublic = $isPublic;
115
		$this->downloadAttachment = $downloadAttachment;
116
		$this->previewManager = $previewManager;
117
	}
118
119
	/**
120
	 * This initializes the plugin.
121
	 *
122
	 * This function is called by \Sabre\DAV\Server, after
123
	 * addPlugin is called.
124
	 *
125
	 * This method should set up the required event subscriptions.
126
	 *
127
	 * @return void
128
	 */
129
	public function initialize(Server $server) {
130
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
131
		$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
132
		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
133
		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
134
		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
135
		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
136
		$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
137
		$server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
138
		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
139
		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
140
		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
141
		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
142
		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
143
		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
144
		$server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
145
		$server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
146
		$server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME;
147
		$server->protectedProperties[] = self::SHARE_NOTE;
148
149
		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
150
		$allowedProperties = ['{DAV:}getetag'];
151
		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
152
153
		$this->server = $server;
154
		$this->server->on('propFind', [$this, 'handleGetProperties']);
155
		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
156
		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
157
		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
158
		$this->server->on('afterMethod:GET', [$this,'httpGet']);
159
		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
160
		$this->server->on('afterResponse', function ($request, ResponseInterface $response) {
161
			$body = $response->getBody();
162
			if (is_resource($body)) {
163
				fclose($body);
164
			}
165
		});
166
		$this->server->on('beforeMove', [$this, 'checkMove']);
167
	}
168
169
	/**
170
	 * Plugin that checks if a move can actually be performed.
171
	 *
172
	 * @param string $source source path
173
	 * @param string $destination destination path
174
	 * @throws Forbidden
175
	 * @throws NotFound
176
	 */
177
	public function checkMove($source, $destination) {
178
		$sourceNode = $this->tree->getNodeForPath($source);
179
		if (!$sourceNode instanceof Node) {
180
			return;
181
		}
182
		[$sourceDir,] = \Sabre\Uri\split($source);
183
		[$destinationDir,] = \Sabre\Uri\split($destination);
184
185
		if ($sourceDir !== $destinationDir) {
186
			$sourceNodeFileInfo = $sourceNode->getFileInfo();
187
			if ($sourceNodeFileInfo === null) {
188
				throw new NotFound($source . ' does not exist');
189
			}
190
191
			if (!$sourceNodeFileInfo->isDeletable()) {
192
				throw new Forbidden($source . " cannot be deleted");
193
			}
194
		}
195
	}
196
197
	/**
198
	 * This sets a cookie to be able to recognize the start of the download
199
	 * the content must not be longer than 32 characters and must only contain
200
	 * alphanumeric characters
201
	 *
202
	 * @param RequestInterface $request
203
	 * @param ResponseInterface $response
204
	 */
205
	public function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

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

205
	public function handleDownloadToken(RequestInterface $request, /** @scrutinizer ignore-unused */ ResponseInterface $response) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
206
		$queryParams = $request->getQueryParameters();
207
208
		/**
209
		 * this sets a cookie to be able to recognize the start of the download
210
		 * the content must not be longer than 32 characters and must only contain
211
		 * alphanumeric characters
212
		 */
213
		if (isset($queryParams['downloadStartSecret'])) {
214
			$token = $queryParams['downloadStartSecret'];
215
			if (!isset($token[32])
216
				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
217
				// FIXME: use $response->setHeader() instead
218
				setcookie('ocDownloadStarted', $token, time() + 20, '/');
219
			}
220
		}
221
	}
222
223
	/**
224
	 * Add headers to file download
225
	 *
226
	 * @param RequestInterface $request
227
	 * @param ResponseInterface $response
228
	 */
229
	public function httpGet(RequestInterface $request, ResponseInterface $response) {
230
		// Only handle valid files
231
		$node = $this->tree->getNodeForPath($request->getPath());
232
		if (!($node instanceof IFile)) {
233
			return;
234
		}
235
236
		// adds a 'Content-Disposition: attachment' header in case no disposition
237
		// header has been set before
238
		if ($this->downloadAttachment &&
239
			$response->getHeader('Content-Disposition') === null) {
240
			$filename = $node->getName();
241
			if ($this->request->isUserAgent(
242
				[
243
					Request::USER_AGENT_IE,
244
					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
245
					Request::USER_AGENT_FREEBOX,
246
				])) {
247
				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
248
			} else {
249
				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
250
													 . '; filename="' . rawurlencode($filename) . '"');
251
			}
252
		}
253
254
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
255
			//Add OC-Checksum header
256
			$checksum = $node->getChecksum();
257
			if ($checksum !== null && $checksum !== '') {
258
				$response->addHeader('OC-Checksum', $checksum);
259
			}
260
		}
261
		$response->addHeader('X-Accel-Buffering', 'no');
262
	}
263
264
	/**
265
	 * Adds all ownCloud-specific properties
266
	 *
267
	 * @param PropFind $propFind
268
	 * @param \Sabre\DAV\INode $node
269
	 * @return void
270
	 */
271
	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
272
		$httpRequest = $this->server->httpRequest;
273
274
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
275
			/**
276
			 * This was disabled, because it made dir listing throw an exception,
277
			 * so users were unable to navigate into folders where one subitem
278
			 * is blocked by the files_accesscontrol app, see:
279
			 * https://github.com/nextcloud/files_accesscontrol/issues/65
280
			 * if (!$node->getFileInfo()->isReadable()) {
281
			 *     // avoid detecting files through this means
282
			 *     throw new NotFound();
283
			 * }
284
			 */
285
286
			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
287
				return $node->getFileId();
288
			});
289
290
			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
291
				return $node->getInternalFileId();
292
			});
293
294
			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
295
				$perms = $node->getDavPermissions();
296
				if ($this->isPublic) {
297
					// remove mount information
298
					$perms = str_replace(['S', 'M'], '', $perms);
299
				}
300
				return $perms;
301
			});
302
303
			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
0 ignored issues
show
Unused Code introduced by
The import $httpRequest is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
304
				$user = $this->userSession->getUser();
305
				if ($user === null) {
306
					return null;
307
				}
308
				return $node->getSharePermissions(
309
					$user->getUID()
310
				);
311
			});
312
313
			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest): ?string {
0 ignored issues
show
Unused Code introduced by
The import $httpRequest is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
314
				$user = $this->userSession->getUser();
315
				if ($user === null) {
316
					return null;
317
				}
318
				$ncPermissions = $node->getSharePermissions(
319
					$user->getUID()
320
				);
321
				$ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
322
				return json_encode($ocmPermissions, JSON_THROW_ON_ERROR);
323
			});
324
325
			$propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
0 ignored issues
show
Unused Code introduced by
The import $httpRequest is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
326
				return json_encode($node->getShareAttributes(), JSON_THROW_ON_ERROR);
327
			});
328
329
			$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
330
				return $node->getETag();
331
			});
332
333
			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node): ?string {
334
				$owner = $node->getOwner();
335
				if (!$owner) {
336
					return null;
337
				} else {
338
					return $owner->getUID();
339
				}
340
			});
341
			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node): ?string {
342
				$owner = $node->getOwner();
343
				if (!$owner) {
344
					return null;
345
				} else {
346
					return $owner->getDisplayName();
347
				}
348
			});
349
350
			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
351
				return json_encode($this->previewManager->isAvailable($node->getFileInfo()), JSON_THROW_ON_ERROR);
352
			});
353
			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): int|float {
354
				return $node->getSize();
355
			});
356
			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
357
				return $node->getFileInfo()->getMountPoint()->getMountType();
358
			});
359
360
			$propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest): ?string {
0 ignored issues
show
Unused Code introduced by
The import $httpRequest is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
361
				$user = $this->userSession->getUser();
362
				if ($user === null) {
363
					return null;
364
				}
365
				return $node->getNoteFromShare(
366
					$user->getUID()
367
				);
368
			});
369
370
			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () use ($node) {
0 ignored issues
show
Unused Code introduced by
The import $node is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
371
				return $this->config->getSystemValue('data-fingerprint', '');
372
			});
373
			$propFind->handle(self::CREATIONDATE_PROPERTYNAME, function () use ($node) {
374
				return (new \DateTimeImmutable())
375
					->setTimestamp($node->getFileInfo()->getCreationTime())
376
					->format(\DateTimeInterface::ATOM);
377
			});
378
			$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
379
				return $node->getFileInfo()->getCreationTime();
380
			});
381
			/**
382
			 * Return file/folder name as displayname. The primary reason to
383
			 * implement it this way is to avoid costly fallback to
384
			 * CustomPropertiesBackend (esp. visible when querying all files
385
			 * in a folder).
386
			 */
387
			$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
388
				return $node->getName();
389
			});
390
		}
391
392
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
393
			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
394
				try {
395
					$directDownloadUrl = $node->getDirectDownload();
396
					if (isset($directDownloadUrl['url'])) {
397
						return $directDownloadUrl['url'];
398
					}
399
				} catch (StorageNotAvailableException $e) {
400
					return false;
401
				} catch (ForbiddenException $e) {
402
					return false;
403
				}
404
				return false;
405
			});
406
407
			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
408
				$checksum = $node->getChecksum();
409
				if ($checksum === null || $checksum === '') {
410
					return null;
411
				}
412
413
				return new ChecksumList($checksum);
414
			});
415
416
			$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
417
				return $node->getFileInfo()->getUploadTime();
418
			});
419
420
			if ($this->config->getSystemValueBool('enable_file_metadata', true)) {
421
				$propFind->handle(self::FILE_METADATA_SIZE, function () use ($node) {
422
					if (!str_starts_with($node->getFileInfo()->getMimetype(), 'image')) {
423
						return json_encode((object)[], JSON_THROW_ON_ERROR);
424
					}
425
426
					if ($node->hasMetadata('size')) {
427
						$sizeMetadata = $node->getMetadata('size');
428
					} else {
429
						// This code path should not be called since we try to preload
430
						// the metadata when loading the folder or the search results
431
						// in one go
432
						$metadataManager = \OC::$server->get(IMetadataManager::class);
433
						$sizeMetadata = $metadataManager->fetchMetadataFor('size', [$node->getId()])[$node->getId()];
434
435
						// TODO would be nice to display this in the profiler...
436
						\OC::$server->get(LoggerInterface::class)->debug('Inefficient fetching of metadata');
437
					}
438
439
					return $sizeMetadata->getValue();
440
				});
441
			}
442
		}
443
444
		if ($node instanceof Directory) {
445
			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
446
				return $node->getSize();
447
			});
448
449
			$propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function () use ($node) {
450
				return $node->getFileInfo()->isEncrypted() ? '1' : '0';
451
			});
452
453
			$requestProperties = $propFind->getRequestedProperties();
454
455
			// TODO detect dynamically which metadata groups are requested and
456
			// preload all of them and not just size
457
			if ($this->config->getSystemValueBool('enable_file_metadata', true)
458
				&& in_array(self::FILE_METADATA_SIZE, $requestProperties, true)) {
459
				// Preloading of the metadata
460
				$fileIds = [];
461
				foreach ($node->getChildren() as $child) {
462
					/** @var \OCP\Files\Node|Node $child */
463
					if (str_starts_with($child->getFileInfo()->getMimeType(), 'image/')) {
0 ignored issues
show
Bug introduced by
The method getFileInfo() does not exist on OCP\Files\Node. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\Files\File or OCP\Files\Folder or OCP\Files\IRootFolder. Are you sure you never get one of those? ( Ignorable by Annotation )

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

463
					if (str_starts_with($child->/** @scrutinizer ignore-call */ getFileInfo()->getMimeType(), 'image/')) {
Loading history...
464
						/** @var File $child */
465
						$fileIds[] = $child->getFileInfo()->getId();
466
					}
467
				}
468
				/** @var IMetaDataManager $metadataManager */
469
				$metadataManager = \OC::$server->get(IMetadataManager::class);
470
				$preloadedMetadata = $metadataManager->fetchMetadataFor('size', $fileIds);
471
				foreach ($node->getChildren() as $child) {
472
					/** @var \OCP\Files\Node|Node $child */
473
					if (str_starts_with($child->getFileInfo()->getMimeType(), 'image')) {
474
						/** @var File $child */
475
						$child->setMetadata('size', $preloadedMetadata[$child->getFileInfo()->getId()]);
476
					}
477
				}
478
			}
479
480
			if (in_array(self::SUBFILE_COUNT_PROPERTYNAME, $requestProperties, true)
481
				|| in_array(self::SUBFOLDER_COUNT_PROPERTYNAME, $requestProperties, true)) {
482
				$nbFiles = 0;
483
				$nbFolders = 0;
484
				foreach ($node->getChildren() as $child) {
485
					if ($child instanceof File) {
486
						$nbFiles++;
487
					} elseif ($child instanceof Directory) {
488
						$nbFolders++;
489
					}
490
				}
491
492
				$propFind->handle(self::SUBFILE_COUNT_PROPERTYNAME, $nbFiles);
493
				$propFind->handle(self::SUBFOLDER_COUNT_PROPERTYNAME, $nbFolders);
494
			}
495
		}
496
	}
497
498
	/**
499
	 * translate Nextcloud permissions to OCM Permissions
500
	 *
501
	 * @param $ncPermissions
502
	 * @return array
503
	 */
504
	protected function ncPermissions2ocmPermissions($ncPermissions) {
505
		$ocmPermissions = [];
506
507
		if ($ncPermissions & Constants::PERMISSION_SHARE) {
508
			$ocmPermissions[] = 'share';
509
		}
510
511
		if ($ncPermissions & Constants::PERMISSION_READ) {
512
			$ocmPermissions[] = 'read';
513
		}
514
515
		if (($ncPermissions & Constants::PERMISSION_CREATE) ||
516
			($ncPermissions & Constants::PERMISSION_UPDATE)) {
517
			$ocmPermissions[] = 'write';
518
		}
519
520
		return $ocmPermissions;
521
	}
522
523
	/**
524
	 * Update ownCloud-specific properties
525
	 *
526
	 * @param string $path
527
	 * @param PropPatch $propPatch
528
	 *
529
	 * @return void
530
	 */
531
	public function handleUpdateProperties($path, PropPatch $propPatch) {
532
		$node = $this->tree->getNodeForPath($path);
533
		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
534
			return;
535
		}
536
537
		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
538
			if (empty($time)) {
539
				return false;
540
			}
541
			$node->touch($time);
542
			return true;
543
		});
544
		$propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
545
			if (empty($etag)) {
546
				return false;
547
			}
548
			return $node->setEtag($etag) !== -1;
549
		});
550
		$propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function ($time) use ($node) {
551
			if (empty($time)) {
552
				return false;
553
			}
554
			$dateTime = new \DateTimeImmutable($time);
555
			$node->setCreationTime($dateTime->getTimestamp());
556
			return true;
557
		});
558
		$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
559
			if (empty($time)) {
560
				return false;
561
			}
562
			$node->setCreationTime((int) $time);
563
			return true;
564
		});
565
		/**
566
		 * Disable modification of the displayname property for files and
567
		 * folders via PROPPATCH. See PROPFIND for more information.
568
		 */
569
		$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
0 ignored issues
show
Unused Code introduced by
The parameter $displayName is not used and could be removed. ( Ignorable by Annotation )

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

569
		$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function (/** @scrutinizer ignore-unused */ $displayName) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
570
			return 403;
571
		});
572
	}
573
574
	/**
575
	 * @param string $filePath
576
	 * @param \Sabre\DAV\INode $node
577
	 * @throws \Sabre\DAV\Exception\BadRequest
578
	 */
579
	public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed. ( Ignorable by Annotation )

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

579
	public function sendFileIdHeader($filePath, /** @scrutinizer ignore-unused */ \Sabre\DAV\INode $node = null) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
580
		// chunked upload handling
581
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
582
			[$path, $name] = \Sabre\Uri\split($filePath);
583
			$info = \OC_FileChunking::decodeName($name);
584
			if (!empty($info)) {
585
				$filePath = $path . '/' . $info['name'];
586
			}
587
		}
588
589
		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
590
		if (!$this->server->tree->nodeExists($filePath)) {
591
			return;
592
		}
593
		$node = $this->server->tree->getNodeForPath($filePath);
594
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
595
			$fileId = $node->getFileId();
596
			if (!is_null($fileId)) {
597
				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
598
			}
599
		}
600
	}
601
}
602