Passed
Push — master ( 90401e...63cb31 )
by Roeland
20:41 queued 20s
created

Folder::getAbsolutePath()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 7
nop 2
dl 0
loc 18
rs 9.4888
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Vincent Petry <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC\Files\Node;
28
29
use OC\DB\QueryBuilder\Literal;
30
use OCA\Files_Sharing\SharedStorage;
31
use OCP\DB\QueryBuilder\IQueryBuilder;
32
use OCP\Files\Config\ICachedMountInfo;
33
use OCP\Files\FileInfo;
34
use OCP\Files\Mount\IMountPoint;
35
use OCP\Files\NotFoundException;
36
use OCP\Files\NotPermittedException;
37
use OCP\Files\Search\ISearchQuery;
38
39
class Folder extends Node implements \OCP\Files\Folder {
40
	/**
41
	 * Creates a Folder that represents a non-existing path
42
	 *
43
	 * @param string $path path
44
	 * @return string non-existing node class
45
	 */
46
	protected function createNonExistingNode($path) {
47
		return new NonExistingFolder($this->root, $this->view, $path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OC\Files\Node...ot, $this->view, $path) returns the type OC\Files\Node\NonExistingFolder which is incompatible with the documented return type string.
Loading history...
48
	}
49
50
	/**
51
	 * @param string $path path relative to the folder
52
	 * @return string
53
	 * @throws \OCP\Files\NotPermittedException
54
	 */
55
	public function getFullPath($path) {
56
		if (!$this->isValidPath($path)) {
57
			throw new NotPermittedException('Invalid path');
58
		}
59
		return $this->path . $this->normalizePath($path);
60
	}
61
62
	/**
63
	 * @param string $path
64
	 * @return string
65
	 */
66
	public function getRelativePath($path) {
67
		if ($this->path === '' or $this->path === '/') {
68
			return $this->normalizePath($path);
69
		}
70
		if ($path === $this->path) {
71
			return '/';
72
		} else if (strpos($path, $this->path . '/') !== 0) {
73
			return null;
74
		} else {
75
			$path = substr($path, strlen($this->path));
76
			return $this->normalizePath($path);
77
		}
78
	}
79
80
	/**
81
	 * check if a node is a (grand-)child of the folder
82
	 *
83
	 * @param \OC\Files\Node\Node $node
84
	 * @return bool
85
	 */
86
	public function isSubNode($node) {
87
		return strpos($node->getPath(), $this->path . '/') === 0;
88
	}
89
90
	/**
91
	 * get the content of this directory
92
	 *
93
	 * @throws \OCP\Files\NotFoundException
94
	 * @return Node[]
95
	 */
96
	public function getDirectoryListing() {
97
		$folderContent = $this->view->getDirectoryContent($this->path);
98
99
		return array_map(function (FileInfo $info) {
100
			if ($info->getMimetype() === 'httpd/unix-directory') {
101
				return new Folder($this->root, $this->view, $info->getPath(), $info);
102
			} else {
103
				return new File($this->root, $this->view, $info->getPath(), $info);
104
			}
105
		}, $folderContent);
106
	}
107
108
	/**
109
	 * @param string $path
110
	 * @param FileInfo $info
111
	 * @return File|Folder
112
	 */
113
	protected function createNode($path, FileInfo $info = null) {
114
		if (is_null($info)) {
115
			$isDir = $this->view->is_dir($path);
116
		} else {
117
			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
118
		}
119
		if ($isDir) {
120
			return new Folder($this->root, $this->view, $path, $info);
121
		} else {
122
			return new File($this->root, $this->view, $path, $info);
123
		}
124
	}
125
126
	/**
127
	 * Get the node at $path
128
	 *
129
	 * @param string $path
130
	 * @return \OC\Files\Node\Node
131
	 * @throws \OCP\Files\NotFoundException
132
	 */
133
	public function get($path) {
134
		return $this->root->get($this->getFullPath($path));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->root->get(...is->getFullPath($path)) returns the type string which is incompatible with the documented return type OC\Files\Node\Node.
Loading history...
135
	}
136
137
	/**
138
	 * @param string $path
139
	 * @return bool
140
	 */
141
	public function nodeExists($path) {
142
		try {
143
			$this->get($path);
144
			return true;
145
		} catch (NotFoundException $e) {
146
			return false;
147
		}
148
	}
149
150
	/**
151
	 * @param string $path
152
	 * @return \OC\Files\Node\Folder
153
	 * @throws \OCP\Files\NotPermittedException
154
	 */
155
	public function newFolder($path) {
156
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
157
			$fullPath = $this->getFullPath($path);
158
			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
159
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
160
			if(!$this->view->mkdir($fullPath)) {
161
				throw new NotPermittedException('Could not create folder');
162
			}
163
			$node = new Folder($this->root, $this->view, $fullPath);
164
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
165
			return $node;
166
		} else {
167
			throw new NotPermittedException('No create permission for folder');
168
		}
169
	}
170
171
	/**
172
	 * @param string $path
173
	 * @return \OC\Files\Node\File
174
	 * @throws \OCP\Files\NotPermittedException
175
	 */
176
	public function newFile($path) {
177
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
178
			$fullPath = $this->getFullPath($path);
179
			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
180
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
181
			if (!$this->view->touch($fullPath)) {
182
				throw new NotPermittedException('Could not create path');
183
			}
184
			$node = new File($this->root, $this->view, $fullPath);
185
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
186
			return $node;
187
		}
188
		throw new NotPermittedException('No create permission for path');
189
	}
190
191
	/**
192
	 * search for files with the name matching $query
193
	 *
194
	 * @param string|ISearchQuery $query
195
	 * @return \OC\Files\Node\Node[]
196
	 */
197
	public function search($query) {
198
		if (is_string($query)) {
199
			return $this->searchCommon('search', array('%' . $query . '%'));
200
		} else {
201
			return $this->searchCommon('searchQuery', array($query));
202
		}
203
	}
204
205
	/**
206
	 * search for files by mimetype
207
	 *
208
	 * @param string $mimetype
209
	 * @return Node[]
210
	 */
211
	public function searchByMime($mimetype) {
212
		return $this->searchCommon('searchByMime', array($mimetype));
213
	}
214
215
	/**
216
	 * search for files by tag
217
	 *
218
	 * @param string|int $tag name or tag id
219
	 * @param string $userId owner of the tags
220
	 * @return Node[]
221
	 */
222
	public function searchByTag($tag, $userId) {
223
		return $this->searchCommon('searchByTag', array($tag, $userId));
224
	}
225
226
	/**
227
	 * @param string $method cache method
228
	 * @param array $args call args
229
	 * @return \OC\Files\Node\Node[]
230
	 */
231
	private function searchCommon($method, $args) {
232
		$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
233
		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
234
			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
235
		}
236
237
		$files = array();
238
		$rootLength = strlen($this->path);
239
		$mount = $this->root->getMount($this->path);
240
		$storage = $mount->getStorage();
241
		$internalPath = $mount->getInternalPath($this->path);
242
		$internalPath = rtrim($internalPath, '/');
243
		if ($internalPath !== '') {
244
			$internalPath = $internalPath . '/';
245
		}
246
		$internalRootLength = strlen($internalPath);
247
248
		$cache = $storage->getCache('');
249
250
		$results = call_user_func_array(array($cache, $method), $args);
251
		foreach ($results as $result) {
252
			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
253
				$result['internalPath'] = $result['path'];
254
				$result['path'] = substr($result['path'], $internalRootLength);
255
				$result['storage'] = $storage;
256
				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
257
			}
258
		}
259
260
		if (!$limitToHome) {
261
			$mounts = $this->root->getMountsIn($this->path);
262
			foreach ($mounts as $mount) {
263
				$storage = $mount->getStorage();
264
				if ($storage) {
265
					$cache = $storage->getCache('');
266
267
					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
268
					$results = call_user_func_array([$cache, $method], $args);
269
					foreach ($results as $result) {
270
						$result['internalPath'] = $result['path'];
271
						$result['path'] = $relativeMountPoint . $result['path'];
272
						$result['storage'] = $storage;
273
						$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
274
							$result['internalPath'], $result, $mount);
275
					}
276
				}
277
			}
278
		}
279
280
		return array_map(function (FileInfo $file) {
281
			return $this->createNode($file->getPath(), $file);
282
		}, $files);
283
	}
284
285
	/**
286
	 * @param int $id
287
	 * @return \OC\Files\Node\Node[]
288
	 */
289
	public function getById($id) {
290
		$mountCache = $this->root->getUserMountCache();
291
		if (strpos($this->getPath(), '/', 1) > 0) {
292
			list(, $user) = explode('/', $this->getPath());
293
		} else {
294
			$user = null;
295
		}
296
		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
297
		$mounts = $this->root->getMountsIn($this->path);
298
		$mounts[] = $this->root->getMount($this->path);
299
		/** @var IMountPoint[] $folderMounts */
300
		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
301
			return $mountPoint->getMountPoint();
302
		}, $mounts), $mounts);
303
304
		/** @var ICachedMountInfo[] $mountsContainingFile */
305
		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
306
			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
307
		}));
308
309
		if (count($mountsContainingFile) === 0) {
310
			if ($user === $this->getAppDataDirectoryName()) {
311
				return $this->getByIdInRootMount((int) $id);
312
			}
313
			return [];
314
		}
315
316
		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
317
			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
318
			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
319
			if (!$cacheEntry) {
320
				return null;
321
			}
322
323
			// cache jails will hide the "true" internal path
324
			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
325
			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
326
			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
327
			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
328
			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
329
				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
330
				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
331
			));
332
		}, $mountsContainingFile);
333
334
		$nodes = array_filter($nodes);
335
336
		return array_filter($nodes, function (Node $node) {
337
			return $this->getRelativePath($node->getPath());
338
		});
339
	}
340
341
	protected function getAppDataDirectoryName(): string {
342
		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
343
		return 'appdata_' . $instanceId;
344
	}
345
346
	/**
347
	 * In case the path we are currently in is inside the appdata_* folder,
348
	 * the original getById method does not work, because it can only look inside
349
	 * the user's mount points. But the user has no mount point for the root storage.
350
	 *
351
	 * So in that case we directly check the mount of the root if it contains
352
	 * the id. If it does we check if the path is inside the path we are working
353
	 * in.
354
	 *
355
	 * @param int $id
356
	 * @return array
357
	 */
358
	protected function getByIdInRootMount(int $id): array {
359
		$mount = $this->root->getMount('');
360
		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
361
		if (!$cacheEntry) {
362
			return [];
363
		}
364
365
		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
366
		$currentPath = rtrim($this->path, '/') . '/';
367
368
		if (strpos($absolutePath, $currentPath) !== 0) {
369
			return [];
370
		}
371
372
		return [$this->root->createNode(
373
			$absolutePath, new \OC\Files\FileInfo(
374
				$absolutePath,
375
				$mount->getStorage(),
376
				$cacheEntry->getPath(),
377
				$cacheEntry,
378
				$mount
379
		))];
380
	}
381
382
	public function getFreeSpace() {
383
		return $this->view->free_space($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->view->free_space($this->path) also could return the type false which is incompatible with the return type mandated by OCP\Files\Folder::getFreeSpace() of integer.
Loading history...
384
	}
385
386
	public function delete() {
387
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
388
			$this->sendHooks(array('preDelete'));
389
			$fileInfo = $this->getFileInfo();
390
			$this->view->rmdir($this->path);
391
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
392
			$this->sendHooks(['postDelete'], [$nonExisting]);
393
			$this->exists = false;
0 ignored issues
show
Bug Best Practice introduced by
The property exists does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
394
		} else {
395
			throw new NotPermittedException('No delete permission for path');
396
		}
397
	}
398
399
	/**
400
	 * Add a suffix to the name in case the file exists
401
	 *
402
	 * @param string $name
403
	 * @return string
404
	 * @throws NotPermittedException
405
	 */
406
	public function getNonExistingName($name) {
407
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
408
		return trim($this->getRelativePath($uniqueName), '/');
409
	}
410
411
	/**
412
	 * @param int $limit
413
	 * @param int $offset
414
	 * @return \OCP\Files\Node[]
415
	 */
416
	public function getRecent($limit, $offset = 0) {
417
		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
418
		$mounts = $this->root->getMountsIn($this->path);
419
		$mounts[] = $this->getMountPoint();
420
421
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
422
			return $mount->getStorage();
423
		});
424
		$storageIds = array_map(function (IMountPoint $mount) {
425
			return $mount->getStorage()->getCache()->getNumericStorageId();
426
		}, $mounts);
427
		/** @var IMountPoint[] $mountMap */
428
		$mountMap = array_combine($storageIds, $mounts);
429
		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
430
431
		// Search in batches of 500 entries
432
		$searchLimit = 500;
433
		$results = [];
434
		$searchResultCount = 0;
435
		$count = 0;
436
		do {
437
			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
438
439
			// Exit condition if there are no more results
440
			if (count($searchResult) === 0) {
441
				break;
442
			}
443
444
			$searchResultCount += count($searchResult);
445
446
			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
447
448
			foreach ($parseResult as $result) {
449
				$results[] = $result;
450
			}
451
452
			$offset += $searchLimit;
453
			$count++;
454
		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
455
456
		return array_slice($results, 0, $limit);
457
	}
458
459
	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
460
		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
461
		$query = $builder
462
			->select('f.*')
463
			->from('filecache', 'f')
464
			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
465
			->andWhere($builder->expr()->orX(
466
			// handle non empty folders separate
467
				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
468
				$builder->expr()->eq('f.size', new Literal(0))
469
			))
470
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
471
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
472
			->orderBy('f.mtime', 'DESC')
473
			->setMaxResults($limit)
474
			->setFirstResult($offset);
475
		return $query->execute()->fetchAll();
476
	}
477
478
	private function recentParse($result, $mountMap, $mimetypeLoader) {
479
		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
480
			$mount = $mountMap[$entry['storage']];
481
			$entry['internalPath'] = $entry['path'];
482
			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
483
			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
484
			$path = $this->getAbsolutePath($mount, $entry['path']);
485
			if (is_null($path)) {
486
				return null;
487
			}
488
			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
489
			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
490
		}, $result));
491
492
		return array_values(array_filter($files, function (Node $node) {
493
			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
494
			if (!$cacheEntry) {
495
				return false;
496
			}
497
			$relative = $this->getRelativePath($node->getPath());
498
			return $relative !== null && $relative !== '/'
499
				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
500
		}));
501
	}
502
503
	private function getAbsolutePath(IMountPoint $mount, $path) {
504
		$storage = $mount->getStorage();
505
		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
506
			if ($storage->instanceOfStorage(SharedStorage::class)) {
507
				$storage->getSourceStorage();
0 ignored issues
show
Bug introduced by
The method getSourceStorage() does not exist on OC\Files\Storage\Storage. It seems like you code against a sub-type of OC\Files\Storage\Storage such as OC\Files\Storage\Wrapper\Wrapper. ( Ignorable by Annotation )

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

507
				$storage->/** @scrutinizer ignore-call */ 
508
              getSourceStorage();
Loading history...
508
			}
509
			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
510
			$jailRoot = $storage->getUnjailedPath('');
511
			$rootLength = strlen($jailRoot) + 1;
512
			if ($path === $jailRoot) {
513
				return $mount->getMountPoint();
514
			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
515
				return $mount->getMountPoint() . substr($path, $rootLength);
516
			} else {
517
				return null;
518
			}
519
		} else {
520
			return $mount->getMountPoint() . $path;
521
		}
522
	}
523
}
524