Passed
Push — master ( 524db1...d9cd8b )
by Julius
14:35 queued 12s
created

Node::getParent()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 3
nop 0
dl 0
loc 11
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Julius Härtl <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program. If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
namespace OC\Files\Node;
31
32
use OC\Files\Filesystem;
33
use OC\Files\Mount\MoveableMount;
34
use OC\Files\Utils\PathHelper;
35
use OCP\Files\FileInfo;
36
use OCP\Files\InvalidPathException;
37
use OCP\Files\NotFoundException;
38
use OCP\Files\NotPermittedException;
39
use OCP\Lock\LockedException;
40
use Symfony\Component\EventDispatcher\GenericEvent;
41
42
// FIXME: this class really should be abstract
43
class Node implements \OCP\Files\Node {
44
	/**
45
	 * @var \OC\Files\View $view
46
	 */
47
	protected $view;
48
49
	/**
50
	 * @var \OC\Files\Node\Root $root
51
	 */
52
	protected $root;
53
54
	/**
55
	 * @var string $path
56
	 */
57
	protected $path;
58
59
	protected ?FileInfo $fileInfo;
60
61
	/**
62
	 * @var Node|null
63
	 */
64
	protected $parent;
65
66
	private bool $infoHasSubMountsIncluded;
67
68
	/**
69
	 * @param \OC\Files\View $view
70
	 * @param \OCP\Files\IRootFolder $root
71
	 * @param string $path
72
	 * @param FileInfo $fileInfo
73
	 */
74
	public function __construct($root, $view, $path, $fileInfo = null, ?Node $parent = null, bool $infoHasSubMountsIncluded = true) {
75
		$this->view = $view;
76
		$this->root = $root;
0 ignored issues
show
Documentation Bug introduced by
$root is of type OCP\Files\IRootFolder, but the property $root was declared to be of type OC\Files\Node\Root. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
77
		$this->path = $path;
78
		$this->fileInfo = $fileInfo;
79
		$this->parent = $parent;
80
		$this->infoHasSubMountsIncluded = $infoHasSubMountsIncluded;
81
	}
82
83
	/**
84
	 * Creates a Node of the same type that represents a non-existing path
85
	 *
86
	 * @param string $path path
87
	 * @return Node non-existing node
88
	 * @throws \Exception
89
	 */
90
	protected function createNonExistingNode($path) {
91
		throw new \Exception('Must be implemented by subclasses');
92
	}
93
94
	/**
95
	 * Returns the matching file info
96
	 *
97
	 * @return FileInfo
98
	 * @throws InvalidPathException
99
	 * @throws NotFoundException
100
	 */
101
	public function getFileInfo(bool $includeMountPoint = true) {
102
		if (!$this->fileInfo) {
103
			if (!Filesystem::isValidPath($this->path)) {
104
				throw new InvalidPathException();
105
			}
106
			$fileInfo = $this->view->getFileInfo($this->path, $includeMountPoint);
107
			$this->infoHasSubMountsIncluded = $includeMountPoint;
108
			if ($fileInfo instanceof FileInfo) {
109
				$this->fileInfo = $fileInfo;
110
			} else {
111
				throw new NotFoundException();
112
			}
113
		} elseif ($includeMountPoint && !$this->infoHasSubMountsIncluded && $this instanceof Folder) {
114
			if ($this->fileInfo instanceof \OC\Files\FileInfo) {
115
				$this->view->addSubMounts($this->fileInfo);
116
			}
117
			$this->infoHasSubMountsIncluded = true;
118
		}
119
		return $this->fileInfo;
120
	}
121
122
	/**
123
	 * @param string[] $hooks
124
	 */
125
	protected function sendHooks($hooks, array $args = null) {
126
		$args = !empty($args) ? $args : [$this];
127
		$dispatcher = \OC::$server->getEventDispatcher();
128
		foreach ($hooks as $hook) {
129
			$this->root->emit('\OC\Files', $hook, $args);
130
			$dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($args). ( Ignorable by Annotation )

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

130
			$dispatcher->/** @scrutinizer ignore-call */ 
131
                dispatch('\OCP\Files::' . $hook, new GenericEvent($args));

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
'\OCP\Files::' . $hook 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

130
			$dispatcher->dispatch(/** @scrutinizer ignore-type */ '\OCP\Files::' . $hook, new GenericEvent($args));
Loading history...
131
		}
132
	}
133
134
	/**
135
	 * @param int $permissions
136
	 * @return bool
137
	 * @throws InvalidPathException
138
	 * @throws NotFoundException
139
	 */
140
	protected function checkPermissions($permissions) {
141
		return ($this->getPermissions() & $permissions) === $permissions;
142
	}
143
144
	public function delete() {
145
	}
146
147
	/**
148
	 * @param int $mtime
149
	 * @throws InvalidPathException
150
	 * @throws NotFoundException
151
	 * @throws NotPermittedException
152
	 */
153
	public function touch($mtime = null) {
154
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) {
155
			$this->sendHooks(['preTouch']);
156
			$this->view->touch($this->path, $mtime);
157
			$this->sendHooks(['postTouch']);
158
			if ($this->fileInfo) {
159
				if (is_null($mtime)) {
160
					$mtime = time();
161
				}
162
				$this->fileInfo['mtime'] = $mtime;
163
			}
164
		} else {
165
			throw new NotPermittedException();
166
		}
167
	}
168
169
	public function getStorage() {
170
		$storage = $this->getMountPoint()->getStorage();
171
		if (!$storage) {
172
			throw new \Exception("No storage for node");
173
		}
174
		return $storage;
175
	}
176
177
	/**
178
	 * @return string
179
	 */
180
	public function getPath() {
181
		return $this->path;
182
	}
183
184
	/**
185
	 * @return string
186
	 */
187
	public function getInternalPath() {
188
		return $this->getFileInfo(false)->getInternalPath();
189
	}
190
191
	/**
192
	 * @return int
193
	 * @throws InvalidPathException
194
	 * @throws NotFoundException
195
	 */
196
	public function getId() {
197
		return $this->getFileInfo(false)->getId() ?? -1;
198
	}
199
200
	/**
201
	 * @return array
202
	 */
203
	public function stat() {
204
		return $this->view->stat($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->view->stat($this->path) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
205
	}
206
207
	/**
208
	 * @return int
209
	 * @throws InvalidPathException
210
	 * @throws NotFoundException
211
	 */
212
	public function getMTime() {
213
		return $this->getFileInfo()->getMTime();
214
	}
215
216
	/**
217
	 * @param bool $includeMounts
218
	 * @return int|float
219
	 * @throws InvalidPathException
220
	 * @throws NotFoundException
221
	 */
222
	public function getSize($includeMounts = true): int|float {
223
		return $this->getFileInfo()->getSize($includeMounts);
224
	}
225
226
	/**
227
	 * @return string
228
	 * @throws InvalidPathException
229
	 * @throws NotFoundException
230
	 */
231
	public function getEtag() {
232
		return $this->getFileInfo()->getEtag();
233
	}
234
235
	/**
236
	 * @return int
237
	 * @throws InvalidPathException
238
	 * @throws NotFoundException
239
	 */
240
	public function getPermissions() {
241
		return $this->getFileInfo(false)->getPermissions();
242
	}
243
244
	/**
245
	 * @return bool
246
	 * @throws InvalidPathException
247
	 * @throws NotFoundException
248
	 */
249
	public function isReadable() {
250
		return $this->getFileInfo(false)->isReadable();
251
	}
252
253
	/**
254
	 * @return bool
255
	 * @throws InvalidPathException
256
	 * @throws NotFoundException
257
	 */
258
	public function isUpdateable() {
259
		return $this->getFileInfo(false)->isUpdateable();
260
	}
261
262
	/**
263
	 * @return bool
264
	 * @throws InvalidPathException
265
	 * @throws NotFoundException
266
	 */
267
	public function isDeletable() {
268
		return $this->getFileInfo(false)->isDeletable();
269
	}
270
271
	/**
272
	 * @return bool
273
	 * @throws InvalidPathException
274
	 * @throws NotFoundException
275
	 */
276
	public function isShareable() {
277
		return $this->getFileInfo(false)->isShareable();
278
	}
279
280
	/**
281
	 * @return bool
282
	 * @throws InvalidPathException
283
	 * @throws NotFoundException
284
	 */
285
	public function isCreatable() {
286
		return $this->getFileInfo(false)->isCreatable();
287
	}
288
289
	/**
290
	 * @return Node
291
	 */
292
	public function getParent() {
293
		if ($this->parent === null) {
294
			$newPath = dirname($this->path);
295
			if ($newPath === '' || $newPath === '.' || $newPath === '/') {
296
				return $this->root;
297
			}
298
299
			$this->parent = $this->root->get($newPath);
300
		}
301
302
		return $this->parent;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parent also could return the type OC\Files\Node\File|OC\Files\Node\Node which is incompatible with the return type mandated by OCP\Files\Node::getParent() of OCP\Files\Folder.
Loading history...
303
	}
304
305
	/**
306
	 * @return string
307
	 */
308
	public function getName() {
309
		return basename($this->path);
310
	}
311
312
	/**
313
	 * @param string $path
314
	 * @return string
315
	 */
316
	protected function normalizePath($path) {
317
		return PathHelper::normalizePath($path);
318
	}
319
320
	/**
321
	 * check if the requested path is valid
322
	 *
323
	 * @param string $path
324
	 * @return bool
325
	 */
326
	public function isValidPath($path) {
327
		if (!$path || $path[0] !== '/') {
328
			$path = '/' . $path;
329
		}
330
		if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
331
			return false;
332
		}
333
		return true;
334
	}
335
336
	public function isMounted() {
337
		return $this->getFileInfo(false)->isMounted();
338
	}
339
340
	public function isShared() {
341
		return $this->getFileInfo(false)->isShared();
342
	}
343
344
	public function getMimeType() {
345
		return $this->getFileInfo(false)->getMimetype();
346
	}
347
348
	public function getMimePart() {
349
		return $this->getFileInfo(false)->getMimePart();
350
	}
351
352
	public function getType() {
353
		return $this->getFileInfo(false)->getType();
354
	}
355
356
	public function isEncrypted() {
357
		return $this->getFileInfo(false)->isEncrypted();
358
	}
359
360
	public function getMountPoint() {
361
		return $this->getFileInfo(false)->getMountPoint();
362
	}
363
364
	public function getOwner() {
365
		return $this->getFileInfo(false)->getOwner();
366
	}
367
368
	public function getChecksum() {
369
	}
370
371
	public function getExtension(): string {
372
		return $this->getFileInfo(false)->getExtension();
373
	}
374
375
	/**
376
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
377
	 * @throws LockedException
378
	 */
379
	public function lock($type) {
380
		$this->view->lockFile($this->path, $type);
381
	}
382
383
	/**
384
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
385
	 * @throws LockedException
386
	 */
387
	public function changeLock($type) {
388
		$this->view->changeLock($this->path, $type);
389
	}
390
391
	/**
392
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
393
	 * @throws LockedException
394
	 */
395
	public function unlock($type) {
396
		$this->view->unlockFile($this->path, $type);
397
	}
398
399
	/**
400
	 * @param string $targetPath
401
	 * @return \OC\Files\Node\Node
402
	 * @throws InvalidPathException
403
	 * @throws NotFoundException
404
	 * @throws NotPermittedException if copy not allowed or failed
405
	 */
406
	public function copy($targetPath) {
407
		$targetPath = $this->normalizePath($targetPath);
408
		$parent = $this->root->get(dirname($targetPath));
409
		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
410
			$nonExisting = $this->createNonExistingNode($targetPath);
411
			$this->sendHooks(['preCopy'], [$this, $nonExisting]);
412
			$this->sendHooks(['preWrite'], [$nonExisting]);
413
			if (!$this->view->copy($this->path, $targetPath)) {
414
				throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
415
			}
416
			$targetNode = $this->root->get($targetPath);
417
			$this->sendHooks(['postCopy'], [$this, $targetNode]);
418
			$this->sendHooks(['postWrite'], [$targetNode]);
419
			return $targetNode;
420
		} else {
421
			throw new NotPermittedException('No permission to copy to path ' . $targetPath);
422
		}
423
	}
424
425
	/**
426
	 * @param string $targetPath
427
	 * @return \OC\Files\Node\Node
428
	 * @throws InvalidPathException
429
	 * @throws NotFoundException
430
	 * @throws NotPermittedException if move not allowed or failed
431
	 * @throws LockedException
432
	 */
433
	public function move($targetPath) {
434
		$targetPath = $this->normalizePath($targetPath);
435
		$parent = $this->root->get(dirname($targetPath));
436
		if (
437
			$parent instanceof Folder and
438
			$this->isValidPath($targetPath) and
439
			(
440
				$parent->isCreatable() ||
441
				($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount)
442
			)
443
		) {
444
			$nonExisting = $this->createNonExistingNode($targetPath);
445
			$this->sendHooks(['preRename'], [$this, $nonExisting]);
446
			$this->sendHooks(['preWrite'], [$nonExisting]);
447
			if (!$this->view->rename($this->path, $targetPath)) {
448
				throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
449
			}
450
451
			$mountPoint = $this->getMountPoint();
452
			if ($mountPoint) {
453
				// update the cached fileinfo with the new (internal) path
454
				/** @var \OC\Files\FileInfo $oldFileInfo */
455
				$oldFileInfo = $this->getFileInfo();
456
				$this->fileInfo = new \OC\Files\FileInfo($targetPath, $oldFileInfo->getStorage(), $mountPoint->getInternalPath($targetPath), $oldFileInfo->getData(), $mountPoint, $oldFileInfo->getOwner());
457
			}
458
459
			$targetNode = $this->root->get($targetPath);
460
			$this->sendHooks(['postRename'], [$this, $targetNode]);
461
			$this->sendHooks(['postWrite'], [$targetNode]);
462
			$this->path = $targetPath;
463
			return $targetNode;
464
		} else {
465
			throw new NotPermittedException('No permission to move to path ' . $targetPath);
466
		}
467
	}
468
469
	public function getCreationTime(): int {
470
		return $this->getFileInfo()->getCreationTime();
471
	}
472
473
	public function getUploadTime(): int {
474
		return $this->getFileInfo()->getUploadTime();
475
	}
476
}
477