Completed
Push — stable8 ( c45eda...dba072 )
by
unknown
15:55
created

View::rename()   F

Complexity

Conditions 30
Paths 620

Size

Total Lines 115
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 83
nc 620
nop 2
dl 0
loc 115
rs 2.1481
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 (c) 2012 Robin Appelman <[email protected]>
4
 * This file is licensed under the Affero General Public License version 3 or
5
 * later.
6
 * See the COPYING-README file.
7
 */
8
9
/**
10
 * Class to provide access to ownCloud filesystem via a "view", and methods for
11
 * working with files within that view (e.g. read, write, delete, etc.). Each
12
 * view is restricted to a set of directories via a virtual root. The default view
13
 * uses the currently logged in user's data directory as root (parts of
14
 * OC_Filesystem are merely a wrapper for OC\Files\View).
15
 *
16
 * Apps that need to access files outside of the user data folders (to modify files
17
 * belonging to a user other than the one currently logged in, for example) should
18
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
19
 * built-in file manipulation functions. This will ensure all hooks and proxies
20
 * are triggered correctly.
21
 *
22
 * Filesystem functions are not called directly; they are passed to the correct
23
 * \OC\Files\Storage\Storage object
24
 */
25
26
namespace OC\Files;
27
28
use OC\Files\Cache\Updater;
29
use OC\Files\Mount\MoveableMount;
30
31
class View {
32
	private $fakeRoot = '';
33
34
	/**
35
	 * @var \OC\Files\Cache\Updater
36
	 */
37
	protected $updater;
38
	
39
	public function __construct($root = '') {
40
		if (is_null($root)) {
41
			throw new \InvalidArgumentException('Root can\'t be null');
42
		}
43
		if(!Filesystem::isValidPath($root)) {
44
			throw new \Exception();
45
		}
46
		
47
		$this->fakeRoot = $root;
48
		$this->updater = new Updater($this);
49
	}
50
51
	public function getAbsolutePath($path = '/') {
52
		if ($path === null) {
53
			return null;
54
		}
55
		$this->assertPathLength($path);
56
		if ($path === '') {
57
			$path = '/';
58
		}
59
		if ($path[0] !== '/') {
60
			$path = '/' . $path;
61
		}
62
		return $this->fakeRoot . $path;
63
	}
64
65
	/**
66
	 * change the root to a fake root
67
	 *
68
	 * @param string $fakeRoot
69
	 * @return boolean|null
70
	 */
71
	public function chroot($fakeRoot) {
72
		if (!$fakeRoot == '') {
73
			if ($fakeRoot[0] !== '/') {
74
				$fakeRoot = '/' . $fakeRoot;
75
			}
76
		}
77
		$this->fakeRoot = $fakeRoot;
78
	}
79
80
	/**
81
	 * get the fake root
82
	 *
83
	 * @return string
84
	 */
85
	public function getRoot() {
86
		return $this->fakeRoot;
87
	}
88
89
	/**
90
	 * get path relative to the root of the view
91
	 *
92
	 * @param string $path
93
	 * @return string
94
	 */
95
	public function getRelativePath($path) {
96
		$this->assertPathLength($path);
97
		if ($this->fakeRoot == '') {
98
			return $path;
99
		}
100
101
		if (rtrim($path,'/') === rtrim($this->fakeRoot, '/')) {
102
			return '/';
103
		}
104
105 View Code Duplication
		if (strpos($path, $this->fakeRoot) !== 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
106
			return null;
107
		} else {
108
			$path = substr($path, strlen($this->fakeRoot));
109
			if (strlen($path) === 0) {
110
				return '/';
111
			} else {
112
				return $path;
113
			}
114
		}
115
	}
116
117
	/**
118
	 * get the mountpoint of the storage object for a path
119
	 * ( note: because a storage is not always mounted inside the fakeroot, the
120
	 * returned mountpoint is relative to the absolute root of the filesystem
121
	 * and doesn't take the chroot into account )
122
	 *
123
	 * @param string $path
124
	 * @return string
125
	 */
126
	public function getMountPoint($path) {
127
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
128
	}
129
130
	/**
131
	 * get the mountpoint of the storage object for a path
132
	 * ( note: because a storage is not always mounted inside the fakeroot, the
133
	 * returned mountpoint is relative to the absolute root of the filesystem
134
	 * and doesn't take the chroot into account )
135
	 *
136
	 * @param string $path
137
	 * @return \OCP\Files\Mount\IMountPoint
138
	 */
139
	public function getMount($path) {
140
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
141
	}
142
143
	/**
144
	 * resolve a path to a storage and internal path
145
	 *
146
	 * @param string $path
147
	 * @return array an array consisting of the storage and the internal path
148
	 */
149
	public function resolvePath($path) {
150
		$a = $this->getAbsolutePath($path);
151
		$p = Filesystem::normalizePath($a);
152
		return Filesystem::resolvePath($p);
153
	}
154
155
	/**
156
	 * return the path to a local version of the file
157
	 * we need this because we can't know if a file is stored local or not from
158
	 * outside the filestorage and for some purposes a local file is needed
159
	 *
160
	 * @param string $path
161
	 * @return string
162
	 */
163 View Code Duplication
	public function getLocalFile($path) {
164
		$parent = substr($path, 0, strrpos($path, '/'));
165
		$path = $this->getAbsolutePath($path);
166
		list($storage, $internalPath) = Filesystem::resolvePath($path);
167
		if (Filesystem::isValidPath($parent) and $storage) {
168
			return $storage->getLocalFile($internalPath);
169
		} else {
170
			return null;
171
		}
172
	}
173
174
	/**
175
	 * @param string $path
176
	 * @return string
177
	 */
178 View Code Duplication
	public function getLocalFolder($path) {
179
		$parent = substr($path, 0, strrpos($path, '/'));
180
		$path = $this->getAbsolutePath($path);
181
		list($storage, $internalPath) = Filesystem::resolvePath($path);
182
		if (Filesystem::isValidPath($parent) and $storage) {
183
			return $storage->getLocalFolder($internalPath);
184
		} else {
185
			return null;
186
		}
187
	}
188
189
	/**
190
	 * the following functions operate with arguments and return values identical
191
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
192
	 * for \OC\Files\Storage\Storage via basicOperation().
193
	 */
194
	public function mkdir($path) {
195
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
196
	}
197
198
	/**
199
	 * remove mount point
200
	 *
201
	 * @param \OC\Files\Mount\MoveableMount $mount
202
	 * @param string $path relative to data/
203
	 * @return boolean
204
	 */
205
	protected function removeMount($mount, $path) {
206
		if ($mount instanceof MoveableMount) {
207
			// cut of /user/files to get the relative path to data/user/files
208
			$pathParts = explode('/', $path, 4);
209
			$relPath = '/' . $pathParts[3];
210
			\OC_Hook::emit(
211
				Filesystem::CLASSNAME, "umount",
212
				array(Filesystem::signal_param_path => $relPath)
213
			);
214
			$result = $mount->removeMount();
215
			if ($result) {
216
				\OC_Hook::emit(
217
					Filesystem::CLASSNAME, "post_umount",
218
					array(Filesystem::signal_param_path => $relPath)
219
				);
220
			}
221
			return $result;
222
		} else {
223
			// do not allow deleting the storage's root / the mount point
224
			// because for some storages it might delete the whole contents
225
			// but isn't supposed to work that way
226
			return false;
227
		}
228
	}
229
230
	/**
231
	 * @param string $path
232
	 * @return bool|mixed
233
	 */
234
	public function rmdir($path) {
235
		$absolutePath = $this->getAbsolutePath($path);
236
		$mount = Filesystem::getMountManager()->find($absolutePath);
237
		if ($mount->getInternalPath($absolutePath) === '') {
238
			return $this->removeMount($mount, $path);
0 ignored issues
show
Documentation introduced by
$mount is of type object<OC\Files\Mount\MountPoint>|null, but the function expects a object<OC\Files\Mount\MoveableMount>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
239
		}
240
		if ($this->is_dir($path)) {
241
			return $this->basicOperation('rmdir', $path, array('delete'));
242
		} else {
243
			return false;
244
		}
245
	}
246
247
	/**
248
	 * @param string $path
249
	 * @return resource
250
	 */
251
	public function opendir($path) {
252
		return $this->basicOperation('opendir', $path, array('read'));
253
	}
254
255
	public function readdir($handle) {
256
		$fsLocal = new Storage\Local(array('datadir' => '/'));
257
		return $fsLocal->readdir($handle);
258
	}
259
260
	public function is_dir($path) {
261
		if ($path == '/') {
262
			return true;
263
		}
264
		return $this->basicOperation('is_dir', $path);
265
	}
266
267
	public function is_file($path) {
268
		if ($path == '/') {
269
			return false;
270
		}
271
		return $this->basicOperation('is_file', $path);
272
	}
273
274
	public function stat($path) {
275
		return $this->basicOperation('stat', $path);
276
	}
277
278
	public function filetype($path) {
279
		return $this->basicOperation('filetype', $path);
280
	}
281
282
	public function filesize($path) {
283
		return $this->basicOperation('filesize', $path);
284
	}
285
286
	public function readfile($path) {
287
		$this->assertPathLength($path);
288
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
289
		$handle = $this->fopen($path, 'rb');
290
		if ($handle) {
291
			$chunkSize = 8192; // 8 kB chunks
292
			while (!feof($handle)) {
293
				echo fread($handle, $chunkSize);
294
				flush();
295
			}
296
			$size = $this->filesize($path);
297
			return $size;
298
		}
299
		return false;
300
	}
301
302
	public function isCreatable($path) {
303
		return $this->basicOperation('isCreatable', $path);
304
	}
305
306
	public function isReadable($path) {
307
		return $this->basicOperation('isReadable', $path);
308
	}
309
310
	public function isUpdatable($path) {
311
		return $this->basicOperation('isUpdatable', $path);
312
	}
313
314
	public function isDeletable($path) {
315
		$absolutePath = $this->getAbsolutePath($path);
316
		$mount = Filesystem::getMountManager()->find($absolutePath);
317
		if ($mount->getInternalPath($absolutePath) === '') {
318
			return $mount instanceof MoveableMount;
319
		}
320
		return $this->basicOperation('isDeletable', $path);
321
	}
322
323
	public function isSharable($path) {
324
		return $this->basicOperation('isSharable', $path);
325
	}
326
327
	public function file_exists($path) {
328
		if ($path == '/') {
329
			return true;
330
		}
331
		return $this->basicOperation('file_exists', $path);
332
	}
333
334
	public function filemtime($path) {
335
		return $this->basicOperation('filemtime', $path);
336
	}
337
338
	public function touch($path, $mtime = null) {
339
		if (!is_null($mtime) and !is_numeric($mtime)) {
340
			$mtime = strtotime($mtime);
341
		}
342
343
		$hooks = array('touch');
344
345
		if (!$this->file_exists($path)) {
346
			$hooks[] = 'create';
347
			$hooks[] = 'write';
348
		}
349
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
350
		if (!$result) {
351
			// If create file fails because of permissions on external storage like SMB folders,
352
			// check file exists and return false if not.
353
			if (!$this->file_exists($path)) {
354
				return false;
355
			}
356
			if (is_null($mtime)) {
357
				$mtime = time();
358
			}
359
			//if native touch fails, we emulate it by changing the mtime in the cache
360
			$this->putFileInfo($path, array('mtime' => $mtime));
361
		}
362
		return true;
363
	}
364
365
	public function file_get_contents($path) {
366
		return $this->basicOperation('file_get_contents', $path, array('read'));
367
	}
368
369
	protected function emit_file_hooks_pre($exists, $path, &$run) {
370
		if (!$exists) {
371
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
372
				Filesystem::signal_param_path => $this->getHookPath($path),
373
				Filesystem::signal_param_run => &$run,
374
			));
375 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
377
				Filesystem::signal_param_path => $this->getHookPath($path),
378
				Filesystem::signal_param_run => &$run,
379
			));
380
		}
381
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
382
			Filesystem::signal_param_path => $this->getHookPath($path),
383
			Filesystem::signal_param_run => &$run,
384
		));
385
	}
386
387
	protected function emit_file_hooks_post($exists, $path) {
388
		if (!$exists) {
389
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
390
				Filesystem::signal_param_path => $this->getHookPath($path),
391
			));
392
		} else {
393
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
394
				Filesystem::signal_param_path => $this->getHookPath($path),
395
			));
396
		}
397
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
398
			Filesystem::signal_param_path => $this->getHookPath($path),
399
		));
400
	}
401
402
	public function file_put_contents($path, $data) {
403
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
404
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
405
			if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data)
406
				and Filesystem::isValidPath($path)
407
				and !Filesystem::isFileBlacklisted($path)
408
			) {
409
				$path = $this->getRelativePath($absolutePath);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath can also be of type boolean; however, OC\Files\View::getRelativePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
410
				$exists = $this->file_exists($path);
411
				$run = true;
412
				if ($this->shouldEmitHooks($path)) {
413
					$this->emit_file_hooks_pre($exists, $path, $run);
414
				}
415
				if (!$run) {
416
					return false;
417
				}
418
				$target = $this->fopen($path, 'w');
419
				if ($target) {
420
					list ($count, $result) = \OC_Helper::streamCopy($data, $target);
0 ignored issues
show
Documentation introduced by
$target is of type string, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
421
					fclose($target);
422
					fclose($data);
423
					$this->updater->update($path);
424
					if ($this->shouldEmitHooks($path) && $result !== false) {
425
						$this->emit_file_hooks_post($exists, $path);
426
					}
427
					\OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
428
					return $result;
429
				} else {
430
					return false;
431
				}
432
			} else {
433
				return false;
434
			}
435
		} else {
436
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
437
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
438
		}
439
	}
440
441
	public function unlink($path) {
442
		if ($path === '' || $path === '/') {
443
			// do not allow deleting the root
444
			return false;
445
		}
446
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
447
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
448
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
449
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
450
			return $this->removeMount($mount, $absolutePath);
0 ignored issues
show
Documentation introduced by
$mount is of type object<OC\Files\Mount\MountPoint>, but the function expects a object<OC\Files\Mount\MoveableMount>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
451
		}
452
		return $this->basicOperation('unlink', $path, array('delete'));
453
	}
454
455
	/**
456
	 * @param string $directory
457
	 * @return bool|mixed
458
	 */
459
	public function deleteAll($directory) {
460
		return $this->rmdir($directory);
461
	}
462
463
	/**
464
	 * Rename/move a file or folder from the source path to target path.
465
	 *
466
	 * @param string $path1 source path
467
	 * @param string $path2 target path
468
	 *
469
	 * @return bool|mixed
470
	 */
471
	public function rename($path1, $path2) {
472
		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
473
		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
474
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
475
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
476
		if (
477
			\OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2)
478
			and Filesystem::isValidPath($path2)
479
			and Filesystem::isValidPath($path1)
480
			and !Filesystem::isFileBlacklisted($path2)
481
		) {
482
			$path1 = $this->getRelativePath($absolutePath1);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath1 can also be of type boolean; however, OC\Files\View::getRelativePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
483
			$path2 = $this->getRelativePath($absolutePath2);
484
			$exists = $this->file_exists($path2);
485
486
			if ($path1 == null or $path2 == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path1 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
It seems like you are loosely comparing $path2 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
487
				return false;
488
			}
489
			$run = true;
490
			if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
491
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
492
				$this->emit_file_hooks_pre($exists, $path2, $run);
493
			} elseif ($this->shouldEmitHooks()) {
494
				\OC_Hook::emit(
495
					Filesystem::CLASSNAME, Filesystem::signal_rename,
496
					array(
497
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
498
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
499
						Filesystem::signal_param_run => &$run
500
					)
501
				);
502
			}
503
			if ($run) {
504
				$mp1 = $this->getMountPoint($path1 . $postFix1);
505
				$mp2 = $this->getMountPoint($path2 . $postFix2);
506
				$manager = Filesystem::getMountManager();
507
				$mount = $manager->find($absolutePath1 . $postFix1);
508
				$storage1 = $mount->getStorage();
509
				$internalPath1 = $mount->getInternalPath($absolutePath1 . $postFix1);
510
				list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
511
				if ($internalPath1 === '' and $mount instanceof MoveableMount) {
512
					if ($this->isTargetAllowed($absolutePath2)) {
513
						/**
514
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount
515
						 */
516
						$sourceMountPoint = $mount->getMountPoint();
517
						$result = $mount->moveMount($absolutePath2);
518
						$manager->moveMount($sourceMountPoint, $mount->getMountPoint());
519
						\OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
520
					} else {
521
						$result = false;
522
					}
523
				} elseif ($mp1 == $mp2) {
524
					if ($storage1) {
525
						$result = $storage1->rename($internalPath1, $internalPath2);
526
						\OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
527
					} else {
528
						$result = false;
529
					}
530
				} else {
531
					if ($this->is_dir($path1)) {
532
						$result = $this->copy($path1, $path2, true);
533
						if ($result === true) {
534
							$result = $storage1->rmdir($internalPath1);
535
						}
536
					} else {
537
						$source = $this->fopen($path1 . $postFix1, 'r');
538
						$target = $this->fopen($path2 . $postFix2, 'w');
539
						list(, $result) = \OC_Helper::streamCopy($source, $target);
0 ignored issues
show
Documentation introduced by
$source is of type false|string|null, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$target is of type false|string|null, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
540
						if ($result !== false) {
541
							$this->touch($path2, $this->filemtime($path1));
542
						}
543
544
						// close open handle - especially $source is necessary because unlink below will
545
						// throw an exception on windows because the file is locked
546
						fclose($source);
547
						fclose($target);
548
549
						if ($result !== false) {
550
							$result &= $storage1->unlink($internalPath1);
551
						} else {
552
							// delete partially written target file
553
							$storage2->unlink($internalPath2);
554
							// delete cache entry that was created by fopen
555
							$storage2->getCache()->remove($internalPath2);
556
						}
557
					}
558
				}
559
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
560
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
561
					$this->updater->update($path2);
562
					if ($this->shouldEmitHooks()) {
563
						$this->emit_file_hooks_post($exists, $path2);
564
					}
565
				} elseif ($result) {
566
					$this->updater->rename($path1, $path2);
567
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
568
						\OC_Hook::emit(
569
							Filesystem::CLASSNAME,
570
							Filesystem::signal_post_rename,
571
							array(
572
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
573
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
574
							)
575
						);
576
					}
577
				}
578
				return $result;
579
			} else {
580
				return false;
581
			}
582
		} else {
583
			return false;
584
		}
585
	}
586
587
	/**
588
	 * Copy a file/folder from the source path to target path
589
	 *
590
	 * @param string $path1 source path
591
	 * @param string $path2 target path
592
	 * @param bool $preserveMtime whether to preserve mtime on the copy
593
	 *
594
	 * @return bool|mixed
595
	 */
596
	public function copy($path1, $path2, $preserveMtime = false) {
597
		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
598
		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
599
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
600
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
601
		if (
602
			\OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2)
603
			and Filesystem::isValidPath($path2)
604
			and Filesystem::isValidPath($path1)
605
			and !Filesystem::isFileBlacklisted($path2)
606
		) {
607
			$path1 = $this->getRelativePath($absolutePath1);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath1 can also be of type boolean; however, OC\Files\View::getRelativePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
608
			$path2 = $this->getRelativePath($absolutePath2);
609
610
			if ($path1 == null or $path2 == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path1 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
It seems like you are loosely comparing $path2 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
611
				return false;
612
			}
613
			$run = true;
614
			$exists = $this->file_exists($path2);
615 View Code Duplication
			if ($this->shouldEmitHooks()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
616
				\OC_Hook::emit(
617
					Filesystem::CLASSNAME,
618
					Filesystem::signal_copy,
619
					array(
620
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
621
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
622
						Filesystem::signal_param_run => &$run
623
					)
624
				);
625
				$this->emit_file_hooks_pre($exists, $path2, $run);
626
			}
627
			if ($run) {
628
				$mp1 = $this->getMountPoint($path1 . $postFix1);
629
				$mp2 = $this->getMountPoint($path2 . $postFix2);
630
				if ($mp1 == $mp2) {
631
					list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
632
					list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
633
					if ($storage) {
634
						$result = $storage->copy($internalPath1, $internalPath2);
635
						if (!$result) {
636
							// delete partially written target file
637
							$storage->unlink($internalPath2);
638
							$storage->getCache()->remove($internalPath2);
639
						}
640
					} else {
641
						$result = false;
642
					}
643
				} else {
644
					if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) {
645
						$result = $this->mkdir($path2);
646
						if ($preserveMtime) {
647
							$this->touch($path2, $this->filemtime($path1));
648
						}
649
						if (is_resource($dh)) {
650 View Code Duplication
							while (($file = readdir($dh)) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
651
								if (!Filesystem::isIgnoredDir($file)) {
652
									if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file, $preserveMtime)) {
653
										$result = false;
654
									}
655
								}
656
							}
657
						}
658
					} else {
659
						list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
660
						$source = $this->fopen($path1 . $postFix1, 'r');
661
						$target = $this->fopen($path2 . $postFix2, 'w');
662
						list(, $result) = \OC_Helper::streamCopy($source, $target);
0 ignored issues
show
Documentation introduced by
$source is of type false|string|null, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$target is of type false|string|null, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
663
						if($result && $preserveMtime) {
664
							$this->touch($path2, $this->filemtime($path1));
665
						}
666
						fclose($source);
667
						fclose($target);
668
						if (!$result) {
669
							// delete partially written target file
670
							$storage2->unlink($internalPath2);
671
							$storage2->getCache()->remove($internalPath2);
672
						}
673
					}
674
				}
675
				$this->updater->update($path2);
676 View Code Duplication
				if ($this->shouldEmitHooks() && $result !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
677
					\OC_Hook::emit(
678
						Filesystem::CLASSNAME,
679
						Filesystem::signal_post_copy,
680
						array(
681
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
682
							Filesystem::signal_param_newpath => $this->getHookPath($path2)
683
						)
684
					);
685
					$this->emit_file_hooks_post($exists, $path2);
686
				}
687
				return $result;
688
			} else {
689
				return false;
690
			}
691
		} else {
692
			return false;
693
		}
694
	}
695
696
	/**
697
	 * @param string $path
698
	 * @param string $mode
699
	 * @return resource
700
	 */
701
	public function fopen($path, $mode) {
702
		$hooks = array();
703
		switch ($mode) {
704
			case 'r':
705
			case 'rb':
706
				$hooks[] = 'read';
707
				break;
708
			case 'r+':
709
			case 'rb+':
710
			case 'w+':
711
			case 'wb+':
712
			case 'x+':
713
			case 'xb+':
714
			case 'a+':
715
			case 'ab+':
716
				$hooks[] = 'read';
717
				$hooks[] = 'write';
718
				break;
719
			case 'w':
720
			case 'wb':
721
			case 'x':
722
			case 'xb':
723
			case 'a':
724
			case 'ab':
725
				$hooks[] = 'write';
726
				break;
727
			default:
728
				\OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR);
729
		}
730
731
		return $this->basicOperation('fopen', $path, $hooks, $mode);
732
	}
733
734
	public function toTmpFile($path) {
735
		$this->assertPathLength($path);
736
		if (Filesystem::isValidPath($path)) {
737
			$source = $this->fopen($path, 'r');
738
			if ($source) {
739
				$extension = pathinfo($path, PATHINFO_EXTENSION);
740
				$tmpFile = \OC_Helper::tmpFile($extension);
741
				file_put_contents($tmpFile, $source);
742
				return $tmpFile;
743
			} else {
744
				return false;
745
			}
746
		} else {
747
			return false;
748
		}
749
	}
750
751
	public function fromTmpFile($tmpFile, $path) {
752
		$this->assertPathLength($path);
753
		if (Filesystem::isValidPath($path)) {
754
755
			// Get directory that the file is going into
756
			$filePath = dirname($path);
757
758
			// Create the directories if any
759
			if (!$this->file_exists($filePath)) {
760
				$this->mkdir($filePath);
761
			}
762
763
			$source = fopen($tmpFile, 'r');
764
			if ($source) {
765
				$result = $this->file_put_contents($path, $source);
766
				// $this->file_put_contents() might have already closed
767
				// the resource, so we check it, before trying to close it
768
				// to avoid messages in the error log.
769
				if (is_resource($source)) {
770
					fclose($source);
771
				}
772
				unlink($tmpFile);
773
				return $result;
774
			} else {
775
				return false;
776
			}
777
		} else {
778
			return false;
779
		}
780
	}
781
782
	public function getMimeType($path) {
783
		$this->assertPathLength($path);
784
		return $this->basicOperation('getMimeType', $path);
785
	}
786
787
	public function hash($type, $path, $raw = false) {
788
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
789
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
790
		if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) {
791
			$path = $this->getRelativePath($absolutePath);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath can also be of type boolean; however, OC\Files\View::getRelativePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
792
			if ($path == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
793
				return false;
794
			}
795 View Code Duplication
			if ($this->shouldEmitHooks($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
796
				\OC_Hook::emit(
797
					Filesystem::CLASSNAME,
798
					Filesystem::signal_read,
799
					array(Filesystem::signal_param_path => $this->getHookPath($path))
800
				);
801
			}
802
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
803
			if ($storage) {
804
				$result = $storage->hash($type, $internalPath, $raw);
805
				$result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
806
				return $result;
807
			}
808
		}
809
		return null;
810
	}
811
812
	public function free_space($path = '/') {
813
		$this->assertPathLength($path);
814
		return $this->basicOperation('free_space', $path);
815
	}
816
817
	/**
818
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
819
	 *
820
	 * @param string $operation
821
	 * @param string $path
822
	 * @param array $hooks (optional)
823
	 * @param mixed $extraParam (optional)
824
	 * @return mixed
825
	 *
826
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
827
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
828
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
829
	 */
830
	private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) {
831
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
832
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
833
		if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam)
834
			and Filesystem::isValidPath($path)
835
			and !Filesystem::isFileBlacklisted($path)
836
		) {
837
			$path = $this->getRelativePath($absolutePath);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath can also be of type boolean; however, OC\Files\View::getRelativePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
838
			if ($path == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
839
				return false;
840
			}
841
842
			$run = $this->runHooks($hooks, $path);
843
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
844
			if ($run and $storage) {
845
				if (!is_null($extraParam)) {
846
					$result = $storage->$operation($internalPath, $extraParam);
847
				} else {
848
					$result = $storage->$operation($internalPath);
849
				}
850
851
				$result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
852
853
				if (in_array('delete', $hooks) and $result) {
854
					$this->updater->remove($path);
855
				}
856
				if (in_array('write', $hooks)) {
857
					$this->updater->update($path);
858
				}
859
				if (in_array('touch', $hooks)) {
860
					$this->updater->update($path, $extraParam);
861
				}
862
863
				if ($this->shouldEmitHooks($path) && $result !== false) {
864
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
865
						$this->runHooks($hooks, $path, true);
866
					}
867
				}
868
				return $result;
869
			}
870
		}
871
		return null;
872
	}
873
874
	/**
875
	 * get the path relative to the default root for hook usage
876
	 *
877
	 * @param string $path
878
	 * @return string
879
	 */
880
	private function getHookPath($path) {
881
		if (!Filesystem::getView()) {
882
			return $path;
883
		}
884
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
885
	}
886
887
	private function shouldEmitHooks($path = '') {
888
		if ($path && Cache\Scanner::isPartialFile($path)) {
889
			return false;
890
		}
891
		if (!Filesystem::$loaded) {
892
			return false;
893
		}
894
		$defaultRoot = Filesystem::getRoot();
895
		if ($defaultRoot === null) {
896
			return false;
897
		}
898
		if ($this->fakeRoot === $defaultRoot) {
899
			return true;
900
		}
901
		$fullPath = $this->getAbsolutePath($path);
902
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
903
	}
904
905
	/**
906
	 * @param string[] $hooks
907
	 * @param string $path
908
	 * @param bool $post
909
	 * @return bool
910
	 */
911
	private function runHooks($hooks, $path, $post = false) {
912
		$relativePath = $path;
913
		$path = $this->getHookPath($path);
914
		$prefix = ($post) ? 'post_' : '';
915
		$run = true;
916
		if ($this->shouldEmitHooks($relativePath)) {
917
			foreach ($hooks as $hook) {
918
				if ($hook != 'read') {
919
					\OC_Hook::emit(
920
						Filesystem::CLASSNAME,
921
						$prefix . $hook,
922
						array(
923
							Filesystem::signal_param_run => &$run,
924
							Filesystem::signal_param_path => $path
925
						)
926
					);
927
				} elseif (!$post) {
928
					\OC_Hook::emit(
929
						Filesystem::CLASSNAME,
930
						$prefix . $hook,
931
						array(
932
							Filesystem::signal_param_path => $path
933
						)
934
					);
935
				}
936
			}
937
		}
938
		return $run;
939
	}
940
941
	/**
942
	 * check if a file or folder has been updated since $time
943
	 *
944
	 * @param string $path
945
	 * @param int $time
946
	 * @return bool
947
	 */
948
	public function hasUpdated($path, $time) {
949
		return $this->basicOperation('hasUpdated', $path, array(), $time);
950
	}
951
952
	/**
953
	 * get the filesystem info
954
	 *
955
	 * @param string $path
956
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
957
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
958
	 * defaults to true
959
	 * @return \OC\Files\FileInfo|false
960
	 */
961
	public function getFileInfo($path, $includeMountPoints = true) {
962
		$this->assertPathLength($path);
963
		$data = array();
964
		if (!Filesystem::isValidPath($path)) {
965
			return $data;
966
		}
967
		if (Cache\Scanner::isPartialFile($path)) {
968
			return $this->getPartFileInfo($path);
969
		}
970
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
971
972
		$mount = Filesystem::getMountManager()->find($path);
973
		$storage = $mount->getStorage();
974
		$internalPath = $mount->getInternalPath($path);
975
		$data = null;
976
		if ($storage) {
977
			$cache = $storage->getCache($internalPath);
978
979
			$data = $cache->get($internalPath);
980
			$watcher = $storage->getWatcher($internalPath);
981
982
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
983
			if (!$data) {
984
				if (!$storage->file_exists($internalPath)) {
985
					return false;
986
				}
987
				$scanner = $storage->getScanner($internalPath);
988
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
989
				$data = $cache->get($internalPath);
990
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->checkUpdate($internalPath, $data)) {
991
				$this->updater->propagate($path);
992
				$data = $cache->get($internalPath);
993
			}
994
995
			if ($data and isset($data['fileid'])) {
996
				// upgrades from oc6 or lower might not have the permissions set in the file cache
997 View Code Duplication
				if ($data['permissions'] === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
998
					$data['permissions'] = $storage->getPermissions($data['path']);
999
					$cache->update($data['fileid'], array('permissions' => $data['permissions']));
1000
				}
1001
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1002
					//add the sizes of other mount points to the folder
1003
					$extOnly = ($includeMountPoints === 'ext');
1004
					$mountPoints = Filesystem::getMountPoints($path);
1005
					foreach ($mountPoints as $mountPoint) {
1006
						$subStorage = Filesystem::getStorage($mountPoint);
1007
						if ($subStorage) {
1008
							// exclude shared storage ?
1009
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1010
								continue;
1011
							}
1012
							$subCache = $subStorage->getCache('');
1013
							$rootEntry = $subCache->get('');
1014
							$data['size'] += isset($rootEntry['size']) ? $rootEntry['size'] : 0;
1015
						}
1016
					}
1017
				}
1018
			}
1019
		}
1020
		if (!$data) {
1021
			return false;
1022
		}
1023
1024
		if ($mount instanceof MoveableMount && $internalPath === '') {
1025
			$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1026
		}
1027
1028
		$data = \OC_FileProxy::runPostProxies('getFileInfo', $path, $data);
1029
1030
		return new FileInfo($path, $storage, $internalPath, $data, $mount);
0 ignored issues
show
Documentation introduced by
$data is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $mount defined by \OC\Files\Filesystem::ge...tManager()->find($path) on line 972 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1031
	}
1032
1033
	/**
1034
	 * get the content of a directory
1035
	 *
1036
	 * @param string $directory path under datadirectory
1037
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1038
	 * @return FileInfo[]
1039
	 */
1040
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1041
		$this->assertPathLength($directory);
1042
		$result = array();
1043
		if (!Filesystem::isValidPath($directory)) {
1044
			return $result;
1045
		}
1046
		$path = $this->getAbsolutePath($directory);
1047
		$path = Filesystem::normalizePath($path);
1048
		$mount = $this->getMount($directory);
1049
		$storage = $mount->getStorage();
1050
		$internalPath = $mount->getInternalPath($path);
1051
		if ($storage) {
1052
			$cache = $storage->getCache($internalPath);
1053
			$user = \OC_User::getUser();
1054
1055
			$data = $cache->get($internalPath);
1056
			$watcher = $storage->getWatcher($internalPath);
1057
			if (!$data or $data['size'] === -1) {
1058
				if (!$storage->file_exists($internalPath)) {
1059
					return array();
1060
				}
1061
				$scanner = $storage->getScanner($internalPath);
1062
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1063
				$data = $cache->get($internalPath);
1064
			} else if ($watcher->checkUpdate($internalPath, $data)) {
1065
				$this->updater->propagate($path);
1066
				$data = $cache->get($internalPath);
1067
			}
1068
1069
			$folderId = $data['fileid'];
1070
			/**
1071
			 * @var \OC\Files\FileInfo[] $files
1072
			 */
1073
			$files = array();
1074
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1075
			foreach ($contents as $content) {
1076 View Code Duplication
				if ($content['permissions'] === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1077
					$content['permissions'] = $storage->getPermissions($content['path']);
1078
					$cache->update($content['fileid'], array('permissions' => $content['permissions']));
1079
				}
1080
				// if sharing was disabled for the user we remove the share permissions
1081
				if (\OCP\Util::isSharingDisabledForUser()) {
1082
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1083
				}
1084
				$files[] = new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($directory) on line 1048 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1085
			}
1086
1087
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1088
			$mounts = Filesystem::getMountManager()->findIn($path);
1089
			$dirLength = strlen($path);
1090
			foreach ($mounts as $mount) {
1091
				$mountPoint = $mount->getMountPoint();
1092
				$subStorage = $mount->getStorage();
1093
				if ($subStorage) {
1094
					$subCache = $subStorage->getCache('');
1095
1096
					if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) {
1097
						$subScanner = $subStorage->getScanner('');
1098
						try {
1099
							$subScanner->scanFile('');
1100
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1101
							continue;
1102
						} catch (\OCP\Files\StorageInvalidException $e) {
1103
							continue;
1104
						} catch (\Exception $e) {
1105
							// sometimes when the storage is not available it can be any exception
1106
							\OCP\Util::writeLog(
1107
								'core',
1108
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1109
								get_class($e) . ': ' . $e->getMessage(),
1110
								\OCP\Util::ERROR
1111
							);
1112
							continue;
1113
						}
1114
					}
1115
1116
					$rootEntry = $subCache->get('');
1117
					if ($rootEntry) {
1118
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1119
						if ($pos = strpos($relativePath, '/')) {
1120
							//mountpoint inside subfolder add size to the correct folder
1121
							$entryName = substr($relativePath, 0, $pos);
1122
							foreach ($files as &$entry) {
1123
								if ($entry['name'] === $entryName) {
1124
									$entry['size'] += $rootEntry['size'];
1125
								}
1126
							}
1127
						} else { //mountpoint in this folder, add an entry for it
1128
							$rootEntry['name'] = $relativePath;
1129
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1130
							$permissions = $rootEntry['permissions'];
1131
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1132
							// for shared files/folders we use the permissions given by the owner
1133
							if ($mount instanceof MoveableMount) {
1134
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1135
							} else {
1136
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1137
							}
1138
1139
							//remove any existing entry with the same name
1140 View Code Duplication
							foreach ($files as $i => $file) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1141
								if ($file['name'] === $rootEntry['name']) {
1142
									unset($files[$i]);
1143
									break;
1144
								}
1145
							}
1146
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1147
1148
							// if sharing was disabled for the user we remove the share permissions
1149
							if (\OCP\Util::isSharingDisabledForUser()) {
1150
								$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
0 ignored issues
show
Bug introduced by
The variable $content seems to be defined by a foreach iteration on line 1075. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
1151
							}
1152
1153
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount);
1154
						}
1155
					}
1156
				}
1157
			}
1158
1159
			if ($mimetype_filter) {
1160
				foreach ($files as $file) {
1161
					if (strpos($mimetype_filter, '/')) {
1162
						if ($file['mimetype'] === $mimetype_filter) {
1163
							$result[] = $file;
1164
						}
1165
					} else {
1166
						if ($file['mimepart'] === $mimetype_filter) {
1167
							$result[] = $file;
1168
						}
1169
					}
1170
				}
1171
			} else {
1172
				$result = $files;
1173
			}
1174
		}
1175
1176
		return $result;
1177
	}
1178
1179
	/**
1180
	 * change file metadata
1181
	 *
1182
	 * @param string $path
1183
	 * @param array|\OCP\Files\FileInfo $data
1184
	 * @return int
1185
	 *
1186
	 * returns the fileid of the updated file
1187
	 */
1188
	public function putFileInfo($path, $data) {
1189
		$this->assertPathLength($path);
1190
		if ($data instanceof FileInfo) {
1191
			$data = $data->getData();
1192
		}
1193
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1194
		/**
1195
		 * @var \OC\Files\Storage\Storage $storage
1196
		 * @var string $internalPath
1197
		 */
1198
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1199
		if ($storage) {
1200
			$cache = $storage->getCache($path);
1201
1202
			if (!$cache->inCache($internalPath)) {
1203
				$scanner = $storage->getScanner($internalPath);
1204
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1205
			}
1206
1207
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1188 can also be of type object<OCP\Files\FileInfo>; however, OC\Files\Cache\Cache::put() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1208
		} else {
1209
			return -1;
1210
		}
1211
	}
1212
1213
	/**
1214
	 * search for files with the name matching $query
1215
	 *
1216
	 * @param string $query
1217
	 * @return FileInfo[]
1218
	 */
1219
	public function search($query) {
1220
		return $this->searchCommon('search', array('%' . $query . '%'));
1221
	}
1222
1223
	/**
1224
	 * search for files with the name matching $query
1225
	 *
1226
	 * @param string $query
1227
	 * @return FileInfo[]
1228
	 */
1229
	public function searchRaw($query) {
1230
		return $this->searchCommon('search', array($query));
1231
	}
1232
1233
	/**
1234
	 * search for files by mimetype
1235
	 *
1236
	 * @param string $mimetype
1237
	 * @return FileInfo[]
1238
	 */
1239
	public function searchByMime($mimetype) {
1240
		return $this->searchCommon('searchByMime', array($mimetype));
1241
	}
1242
1243
	/**
1244
	 * search for files by tag
1245
	 *
1246
	 * @param string|int $tag name or tag id
1247
	 * @param string $userId owner of the tags
1248
	 * @return FileInfo[]
1249
	 */
1250
	public function searchByTag($tag, $userId) {
1251
		return $this->searchCommon('searchByTag', array($tag, $userId));
1252
	}
1253
1254
	/**
1255
	 * @param string $method cache method
1256
	 * @param array $args
1257
	 * @return FileInfo[]
1258
	 */
1259
	private function searchCommon($method, $args) {
1260
		$files = array();
1261
		$rootLength = strlen($this->fakeRoot);
1262
1263
		$mount = $this->getMount('');
1264
		$mountPoint = $mount->getMountPoint();
1265
		$storage = $mount->getStorage();
1266
		if ($storage) {
1267
			$cache = $storage->getCache('');
1268
1269
			$results = call_user_func_array(array($cache, $method), $args);
1270
			foreach ($results as $result) {
1271
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1272
					$internalPath = $result['path'];
1273
					$path = $mountPoint . $result['path'];
1274
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1275
					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount('') on line 1263 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1276
				}
1277
			}
1278
1279
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1280
			foreach ($mounts as $mount) {
1281
				$mountPoint = $mount->getMountPoint();
1282
				$storage = $mount->getStorage();
1283
				if ($storage) {
1284
					$cache = $storage->getCache('');
1285
1286
					$relativeMountPoint = substr($mountPoint, $rootLength);
1287
					$results = call_user_func_array(array($cache, $method), $args);
1288
					if ($results) {
1289
						foreach ($results as $result) {
1290
							$internalPath = $result['path'];
1291
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1292
							$path = rtrim($mountPoint . $internalPath, '/');
1293
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount);
1294
						}
1295
					}
1296
				}
1297
			}
1298
		}
1299
		return $files;
1300
	}
1301
1302
	/**
1303
	 * Get the owner for a file or folder
1304
	 *
1305
	 * @param string $path
1306
	 * @return string
1307
	 */
1308
	public function getOwner($path) {
1309
		return $this->basicOperation('getOwner', $path);
1310
	}
1311
1312
	/**
1313
	 * get the ETag for a file or folder
1314
	 *
1315
	 * @param string $path
1316
	 * @return string
1317
	 */
1318
	public function getETag($path) {
1319
		/**
1320
		 * @var Storage\Storage $storage
1321
		 * @var string $internalPath
1322
		 */
1323
		list($storage, $internalPath) = $this->resolvePath($path);
1324
		if ($storage) {
1325
			return $storage->getETag($internalPath);
1326
		} else {
1327
			return null;
1328
		}
1329
	}
1330
1331
	/**
1332
	 * Get the path of a file by id, relative to the view
1333
	 *
1334
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1335
	 *
1336
	 * @param int $id
1337
	 * @return string|null
1338
	 */
1339
	public function getPath($id) {
1340
		$id = (int)$id;
1341
		$manager = Filesystem::getMountManager();
1342
		$mounts = $manager->findIn($this->fakeRoot);
1343
		$mounts[] = $manager->find($this->fakeRoot);
1344
		// reverse the array so we start with the storage this view is in
1345
		// which is the most likely to contain the file we're looking for
1346
		$mounts = array_reverse($mounts);
1347 View Code Duplication
		foreach ($mounts as $mount) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1348
			/**
1349
			 * @var \OC\Files\Mount\MountPoint $mount
1350
			 */
1351
			if ($mount->getStorage()) {
1352
				$cache = $mount->getStorage()->getCache();
1353
				$internalPath = $cache->getPathById($id);
1354
				if (is_string($internalPath)) {
1355
					$fullPath = $mount->getMountPoint() . $internalPath;
1356
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1357
						return $path;
1358
					}
1359
				}
1360
			}
1361
		}
1362
		return null;
1363
	}
1364
1365
	private function assertPathLength($path) {
1366
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1367
		// Check for the string length - performed using isset() instead of strlen()
1368
		// because isset() is about 5x-40x faster.
1369
		if (isset($path[$maxLen])) {
1370
			$pathLen = strlen($path);
1371
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1372
		}
1373
	}
1374
1375
	/**
1376
	 * check if it is allowed to move a mount point to a given target.
1377
	 * It is not allowed to move a mount point into a different mount point
1378
	 *
1379
	 * @param string $target path
1380
	 * @return boolean
1381
	 */
1382
	private function isTargetAllowed($target) {
1383
1384
		$result = false;
1385
1386
		list($targetStorage,) = \OC\Files\Filesystem::resolvePath($target);
1387
		if ($targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1388
			$result = true;
1389
		} else {
1390
			\OCP\Util::writeLog('files',
1391
				'It is not allowed to move one mount point into another one',
1392
				\OCP\Util::DEBUG);
1393
		}
1394
1395
		return $result;
1396
	}
1397
1398
	/**
1399
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1400
	 *
1401
	 * @param string $path
1402
	 * @return \OCP\Files\FileInfo
1403
	 */
1404
	private function getPartFileInfo($path) {
1405
		$mount = $this->getMount($path);
1406
		$storage = $mount->getStorage();
1407
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1408
		return new FileInfo(
1409
			$this->getAbsolutePath($path),
1410
			$storage,
1411
			$internalPath,
1412
			[
1413
				'fileid' => null,
1414
				'mimetype' => $storage->getMimeType($internalPath),
1415
				'name' => basename($path),
1416
				'etag' => null,
1417
				'size' => $storage->filesize($internalPath),
1418
				'mtime' => $storage->filemtime($internalPath),
1419
				'encrypted' => false,
1420
				'permissions' => \OCP\Constants::PERMISSION_ALL
1421
			],
1422
			$mount
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1405 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1423
		);
1424
	}
1425
1426
	/**
1427
	 * @return Updater
1428
	 */
1429
	public function getUpdater(){
1430
		return $this->updater;
1431
	}
1432
}
1433