Passed
Push — master ( b88dc5...9f34d1 )
by Robin
14:21 queued 11s
created

Node   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 129
dl 0
loc 383
rs 3.36
c 0
b 0
f 0
wmc 63

27 Methods

Rating   Name   Duplication   Size   Complexity  
C getDavPermissions() 0 30 10
A changeLock() 0 2 1
A getId() 0 2 1
A setUploadTime() 0 2 1
A setCreationTime() 0 2 1
A refreshInfo() 0 7 2
A getNode() 0 2 1
A getFileId() 0 8 2
A getETag() 0 2 1
A sanitizeMtime() 0 2 1
B getSharePermissions() 0 49 11
A getPath() 0 2 1
A setPropertyCache() 0 2 1
A getNoteFromShare() 0 23 6
A getOwner() 0 2 1
A releaseLock() 0 2 1
A getSize() 0 2 1
A touch() 0 4 1
A setETag() 0 2 1
A getInternalFileId() 0 2 1
A getLastModified() 0 6 2
A acquireLock() 0 2 1
A getName() 0 2 1
A __construct() 0 17 5
A getFileInfo() 0 2 1
A verifyPath() 0 6 2
A setName() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like Node often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Node, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Daniel Calviño Sánchez <[email protected]>
9
 * @author Jakob Sack <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Klaas Freitag <[email protected]>
13
 * @author Markus Goetz <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Thomas Müller <[email protected]>
18
 * @author Tobias Kaminsky <[email protected]>
19
 * @author Vincent Petry <[email protected]>
20
 *
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program. If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
namespace OCA\DAV\Connector\Sabre;
37
38
use OC\Files\Mount\MoveableMount;
39
use OC\Files\Node\File;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OCA\DAV\Connector\Sabre\File. 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...
40
use OC\Files\Node\Folder;
41
use OC\Files\View;
42
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
43
use OCP\Files\FileInfo;
44
use OCP\Files\IRootFolder;
45
use OCP\Files\StorageNotAvailableException;
46
use OCP\Share\IShare;
47
use OCP\Share\Exceptions\ShareNotFound;
48
use OCP\Share\IManager;
49
50
abstract class Node implements \Sabre\DAV\INode {
51
52
	/**
53
	 * @var \OC\Files\View
54
	 */
55
	protected $fileView;
56
57
	/**
58
	 * The path to the current node
59
	 *
60
	 * @var string
61
	 */
62
	protected $path;
63
64
	/**
65
	 * node properties cache
66
	 *
67
	 * @var array
68
	 */
69
	protected $property_cache = null;
70
71
	/**
72
	 * @var \OCP\Files\FileInfo
73
	 */
74
	protected $info;
75
76
	/**
77
	 * @var IManager
78
	 */
79
	protected $shareManager;
80
81
	protected \OCP\Files\Node $node;
82
83
	/**
84
	 * Sets up the node, expects a full path name
85
	 *
86
	 * @param \OC\Files\View $view
87
	 * @param \OCP\Files\FileInfo $info
88
	 * @param IManager $shareManager
89
	 */
90
	public function __construct(View $view, FileInfo $info, IManager $shareManager = null) {
91
		$this->fileView = $view;
92
		$this->path = $this->fileView->getRelativePath($info->getPath());
93
		$this->info = $info;
94
		if ($shareManager) {
95
			$this->shareManager = $shareManager;
96
		} else {
97
			$this->shareManager = \OC::$server->getShareManager();
98
		}
99
		if ($info instanceof Folder || $info instanceof File) {
100
			$this->node = $info;
101
		} else {
102
			$root = \OC::$server->get(IRootFolder::class);
103
			if ($info->getType() === FileInfo::TYPE_FOLDER) {
104
				$this->node = new Folder($root, $view, $this->path, $info);
105
			} else {
106
				$this->node = new File($root, $view, $this->path, $info);
107
			}
108
		}
109
	}
110
111
	protected function refreshInfo() {
112
		$this->info = $this->fileView->getFileInfo($this->path);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->fileView->getFileInfo($this->path) can also be of type false. However, the property $info is declared as type OCP\Files\FileInfo. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
113
		$root = \OC::$server->get(IRootFolder::class);
114
		if ($this->info->getType() === FileInfo::TYPE_FOLDER) {
115
			$this->node = new Folder($root, $this->fileView, $this->path, $this->info);
116
		} else {
117
			$this->node = new File($root, $this->fileView, $this->path, $this->info);
118
		}
119
	}
120
121
	/**
122
	 *  Returns the name of the node
123
	 *
124
	 * @return string
125
	 */
126
	public function getName() {
127
		return $this->info->getName();
128
	}
129
130
	/**
131
	 * Returns the full path
132
	 *
133
	 * @return string
134
	 */
135
	public function getPath() {
136
		return $this->path;
137
	}
138
139
	/**
140
	 * Renames the node
141
	 *
142
	 * @param string $name The new name
143
	 * @throws \Sabre\DAV\Exception\BadRequest
144
	 * @throws \Sabre\DAV\Exception\Forbidden
145
	 */
146
	public function setName($name) {
147
148
		// rename is only allowed if the update privilege is granted
149
		if (!($this->info->isUpdateable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) {
150
			throw new \Sabre\DAV\Exception\Forbidden();
151
		}
152
153
		[$parentPath,] = \Sabre\Uri\split($this->path);
154
		[, $newName] = \Sabre\Uri\split($name);
155
156
		// verify path of the target
157
		$this->verifyPath();
158
159
		$newPath = $parentPath . '/' . $newName;
160
161
		if (!$this->fileView->rename($this->path, $newPath)) {
162
			throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath);
163
		}
164
165
		$this->path = $newPath;
166
167
		$this->refreshInfo();
168
	}
169
170
	public function setPropertyCache($property_cache) {
171
		$this->property_cache = $property_cache;
172
	}
173
174
	/**
175
	 * Returns the last modification time, as a unix timestamp
176
	 *
177
	 * @return int timestamp as integer
178
	 */
179
	public function getLastModified() {
180
		$timestamp = $this->info->getMtime();
181
		if (!empty($timestamp)) {
182
			return (int)$timestamp;
183
		}
184
		return $timestamp;
185
	}
186
187
	/**
188
	 *  sets the last modification time of the file (mtime) to the value given
189
	 *  in the second parameter or to now if the second param is empty.
190
	 *  Even if the modification time is set to a custom value the access time is set to now.
191
	 */
192
	public function touch($mtime) {
193
		$mtime = $this->sanitizeMtime($mtime);
194
		$this->fileView->touch($this->path, $mtime);
195
		$this->refreshInfo();
196
	}
197
198
	/**
199
	 * Returns the ETag for a file
200
	 *
201
	 * An ETag is a unique identifier representing the current version of the
202
	 * file. If the file changes, the ETag MUST change.  The ETag is an
203
	 * arbitrary string, but MUST be surrounded by double-quotes.
204
	 *
205
	 * Return null if the ETag can not effectively be determined
206
	 *
207
	 * @return string
208
	 */
209
	public function getETag() {
210
		return '"' . $this->info->getEtag() . '"';
211
	}
212
213
	/**
214
	 * Sets the ETag
215
	 *
216
	 * @param string $etag
217
	 *
218
	 * @return int file id of updated file or -1 on failure
219
	 */
220
	public function setETag($etag) {
221
		return $this->fileView->putFileInfo($this->path, ['etag' => $etag]);
222
	}
223
224
	public function setCreationTime(int $time) {
225
		return $this->fileView->putFileInfo($this->path, ['creation_time' => $time]);
226
	}
227
228
	public function setUploadTime(int $time) {
229
		return $this->fileView->putFileInfo($this->path, ['upload_time' => $time]);
230
	}
231
232
	/**
233
	 * Returns the size of the node, in bytes
234
	 *
235
	 * @return integer
236
	 */
237
	public function getSize() {
238
		return $this->info->getSize();
239
	}
240
241
	/**
242
	 * Returns the cache's file id
243
	 *
244
	 * @return int
245
	 */
246
	public function getId() {
247
		return $this->info->getId();
248
	}
249
250
	/**
251
	 * @return string|null
252
	 */
253
	public function getFileId() {
254
		if ($this->info->getId()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->info->getId() of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
255
			$instanceId = \OC_Util::getInstanceId();
256
			$id = sprintf('%08d', $this->info->getId());
257
			return $id . $instanceId;
258
		}
259
260
		return null;
261
	}
262
263
	/**
264
	 * @return integer
265
	 */
266
	public function getInternalFileId() {
267
		return $this->info->getId();
268
	}
269
270
	/**
271
	 * @param string $user
272
	 * @return int
273
	 */
274
	public function getSharePermissions($user) {
275
276
		// check of we access a federated share
277
		if ($user !== null) {
0 ignored issues
show
introduced by
The condition $user !== null is always true.
Loading history...
278
			try {
279
				$share = $this->shareManager->getShareByToken($user);
280
				return $share->getPermissions();
281
			} catch (ShareNotFound $e) {
282
				// ignore
283
			}
284
		}
285
286
		try {
287
			$storage = $this->info->getStorage();
288
		} catch (StorageNotAvailableException $e) {
289
			$storage = null;
290
		}
291
292
		if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
293
			/** @var \OCA\Files_Sharing\SharedStorage $storage */
294
			$permissions = (int)$storage->getShare()->getPermissions();
295
		} else {
296
			$permissions = $this->info->getPermissions();
297
		}
298
299
		/*
300
		 * We can always share non moveable mount points with DELETE and UPDATE
301
		 * Eventually we need to do this properly
302
		 */
303
		$mountpoint = $this->info->getMountPoint();
304
		if (!($mountpoint instanceof MoveableMount)) {
305
			$mountpointpath = $mountpoint->getMountPoint();
306
			if (substr($mountpointpath, -1) === '/') {
307
				$mountpointpath = substr($mountpointpath, 0, -1);
308
			}
309
310
			if (!$mountpoint->getOption('readonly', false) && $mountpointpath === $this->info->getPath()) {
311
				$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
312
			}
313
		}
314
315
		/*
316
		 * Files can't have create or delete permissions
317
		 */
318
		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
319
			$permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE);
320
		}
321
322
		return $permissions;
323
	}
324
325
	/**
326
	 * @param string $user
327
	 * @return string
328
	 */
329
	public function getNoteFromShare($user) {
330
		if ($user === null) {
0 ignored issues
show
introduced by
The condition $user === null is always false.
Loading history...
331
			return '';
332
		}
333
334
		$types = [
335
			IShare::TYPE_USER,
336
			IShare::TYPE_GROUP,
337
			IShare::TYPE_CIRCLE,
338
			IShare::TYPE_ROOM
339
		];
340
341
		foreach ($types as $shareType) {
342
			$shares = $this->shareManager->getSharedWith($user, $shareType, $this, -1);
0 ignored issues
show
Bug introduced by
$this of type OCA\DAV\Connector\Sabre\Node is incompatible with the type OCP\Files\Node|null expected by parameter $node of OCP\Share\IManager::getSharedWith(). ( Ignorable by Annotation )

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

342
			$shares = $this->shareManager->getSharedWith($user, $shareType, /** @scrutinizer ignore-type */ $this, -1);
Loading history...
343
			foreach ($shares as $share) {
344
				$note = $share->getNote();
345
				if ($share->getShareOwner() !== $user && !empty($note)) {
346
					return $note;
347
				}
348
			}
349
		}
350
351
		return '';
352
	}
353
354
	/**
355
	 * @return string
356
	 */
357
	public function getDavPermissions() {
358
		$p = '';
359
		if ($this->info->isShared()) {
360
			$p .= 'S';
361
		}
362
		if ($this->info->isShareable()) {
363
			$p .= 'R';
364
		}
365
		if ($this->info->isMounted()) {
366
			$p .= 'M';
367
		}
368
		if ($this->info->isReadable()) {
369
			$p .= 'G';
370
		}
371
		if ($this->info->isDeletable()) {
372
			$p .= 'D';
373
		}
374
		if ($this->info->isUpdateable()) {
375
			$p .= 'NV'; // Renameable, Moveable
376
		}
377
		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
378
			if ($this->info->isUpdateable()) {
379
				$p .= 'W';
380
			}
381
		} else {
382
			if ($this->info->isCreatable()) {
383
				$p .= 'CK';
384
			}
385
		}
386
		return $p;
387
	}
388
389
	public function getOwner() {
390
		return $this->info->getOwner();
391
	}
392
393
	protected function verifyPath() {
394
		try {
395
			$fileName = basename($this->info->getPath());
396
			$this->fileView->verifyPath($this->path, $fileName);
397
		} catch (\OCP\Files\InvalidPathException $ex) {
398
			throw new InvalidPath($ex->getMessage());
399
		}
400
	}
401
402
	/**
403
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
404
	 */
405
	public function acquireLock($type) {
406
		$this->fileView->lockFile($this->path, $type);
407
	}
408
409
	/**
410
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
411
	 */
412
	public function releaseLock($type) {
413
		$this->fileView->unlockFile($this->path, $type);
414
	}
415
416
	/**
417
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
418
	 */
419
	public function changeLock($type) {
420
		$this->fileView->changeLock($this->path, $type);
421
	}
422
423
	public function getFileInfo() {
424
		return $this->info;
425
	}
426
427
	public function getNode(): \OCP\Files\Node {
428
		return $this->node;
429
	}
430
431
	protected function sanitizeMtime($mtimeFromRequest) {
432
		return MtimeSanitizer::sanitizeMtime($mtimeFromRequest);
433
	}
434
}
435