Passed
Push — master ( 6af36c...3efd9a )
by Roeland
11:04 queued 11s
created

Folder::getAppDataDirectoryName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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\ISearchOperator;
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|ISearchOperator $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
		$files = array();
233
		$rootLength = strlen($this->path);
234
		$mount = $this->root->getMount($this->path);
235
		$storage = $mount->getStorage();
236
		$internalPath = $mount->getInternalPath($this->path);
237
		$internalPath = rtrim($internalPath, '/');
238
		if ($internalPath !== '') {
239
			$internalPath = $internalPath . '/';
240
		}
241
		$internalRootLength = strlen($internalPath);
242
243
		$cache = $storage->getCache('');
244
245
		$results = call_user_func_array(array($cache, $method), $args);
246
		foreach ($results as $result) {
247
			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
248
				$result['internalPath'] = $result['path'];
249
				$result['path'] = substr($result['path'], $internalRootLength);
250
				$result['storage'] = $storage;
251
				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
252
			}
253
		}
254
255
		$mounts = $this->root->getMountsIn($this->path);
256
		foreach ($mounts as $mount) {
257
			$storage = $mount->getStorage();
258
			if ($storage) {
259
				$cache = $storage->getCache('');
260
261
				$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
262
				$results = call_user_func_array(array($cache, $method), $args);
263
				foreach ($results as $result) {
264
					$result['internalPath'] = $result['path'];
265
					$result['path'] = $relativeMountPoint . $result['path'];
266
					$result['storage'] = $storage;
267
					$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
268
				}
269
			}
270
		}
271
272
		return array_map(function (FileInfo $file) {
273
			return $this->createNode($file->getPath(), $file);
274
		}, $files);
275
	}
276
277
	/**
278
	 * @param int $id
279
	 * @return \OC\Files\Node\Node[]
280
	 */
281
	public function getById($id) {
282
		$mountCache = $this->root->getUserMountCache();
283
		if (strpos($this->getPath(), '/', 1) > 0) {
284
			list(, $user) = explode('/', $this->getPath());
285
		} else {
286
			$user = null;
287
		}
288
		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
289
		$mounts = $this->root->getMountsIn($this->path);
290
		$mounts[] = $this->root->getMount($this->path);
291
		/** @var IMountPoint[] $folderMounts */
292
		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
293
			return $mountPoint->getMountPoint();
294
		}, $mounts), $mounts);
295
296
		/** @var ICachedMountInfo[] $mountsContainingFile */
297
		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
298
			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
299
		}));
300
301
		if (count($mountsContainingFile) === 0) {
302
			if ($user === $this->getAppDataDirectoryName()) {
303
				return $this->getByIdInRootMount((int) $id);
304
			}
305
			return [];
306
		}
307
308
		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
309
			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
310
			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
311
			if (!$cacheEntry) {
312
				return null;
313
			}
314
315
			// cache jails will hide the "true" internal path
316
			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
317
			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
318
			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
319
			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
320
			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
321
				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
322
				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
323
			));
324
		}, $mountsContainingFile);
325
326
		$nodes = array_filter($nodes);
327
328
		return array_filter($nodes, function (Node $node) {
329
			return $this->getRelativePath($node->getPath());
330
		});
331
	}
332
333
	protected function getAppDataDirectoryName(): string {
334
		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
335
		return 'appdata_' . $instanceId;
336
	}
337
338
	/**
339
	 * In case the path we are currently in is inside the appdata_* folder,
340
	 * the original getById method does not work, because it can only look inside
341
	 * the user's mount points. But the user has no mount point for the root storage.
342
	 *
343
	 * So in that case we directly check the mount of the root if it contains
344
	 * the id. If it does we check if the path is inside the path we are working
345
	 * in.
346
	 *
347
	 * @param int $id
348
	 * @return array
349
	 */
350
	protected function getByIdInRootMount(int $id): array {
351
		$mount = $this->root->getMount('');
352
		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
353
		if (!$cacheEntry) {
354
			return [];
355
		}
356
357
		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
358
		$currentPath = rtrim($this->path, '/') . '/';
359
360
		if (strpos($absolutePath, $currentPath) !== 0) {
361
			return [];
362
		}
363
364
		return [$this->root->createNode(
365
			$absolutePath, new \OC\Files\FileInfo(
366
				$absolutePath,
367
				$mount->getStorage(),
368
				$cacheEntry->getPath(),
369
				$cacheEntry,
370
				$mount
371
		))];
372
	}
373
374
	public function getFreeSpace() {
375
		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...
376
	}
377
378
	public function delete() {
379
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
380
			$this->sendHooks(array('preDelete'));
381
			$fileInfo = $this->getFileInfo();
382
			$this->view->rmdir($this->path);
383
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
384
			$this->sendHooks(['postDelete'], [$nonExisting]);
385
			$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...
386
		} else {
387
			throw new NotPermittedException('No delete permission for path');
388
		}
389
	}
390
391
	/**
392
	 * Add a suffix to the name in case the file exists
393
	 *
394
	 * @param string $name
395
	 * @return string
396
	 * @throws NotPermittedException
397
	 */
398
	public function getNonExistingName($name) {
399
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
400
		return trim($this->getRelativePath($uniqueName), '/');
401
	}
402
403
	/**
404
	 * @param int $limit
405
	 * @param int $offset
406
	 * @return \OCP\Files\Node[]
407
	 */
408
	public function getRecent($limit, $offset = 0) {
409
		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
410
		$mounts = $this->root->getMountsIn($this->path);
411
		$mounts[] = $this->getMountPoint();
412
413
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
414
			return $mount->getStorage();
415
		});
416
		$storageIds = array_map(function (IMountPoint $mount) {
417
			return $mount->getStorage()->getCache()->getNumericStorageId();
418
		}, $mounts);
419
		/** @var IMountPoint[] $mountMap */
420
		$mountMap = array_combine($storageIds, $mounts);
421
		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
422
423
		// Search in batches of 500 entries
424
		$searchLimit = 500;
425
		$results = [];
426
		$searchResultCount = 0;
427
		$count = 0;
428
		do {
429
			$searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype);
430
431
			// Exit condition if there are no more results
432
			if (count($searchResult) === 0) {
433
				break;
434
			}
435
436
			$searchResultCount += count($searchResult);
437
438
			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
439
440
			foreach ($parseResult as $result) {
441
				$results[] = $result;
442
			}
443
444
			$offset += $searchLimit;
445
			$count++;
446
		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
447
448
		return array_slice($results, 0, $limit);
449
	}
450
451
	private function recentSearch($limit, $offset, $storageIds, $folderMimetype) {
452
		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
453
		$query = $builder
454
			->select('f.*')
455
			->from('filecache', 'f')
456
			->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
457
			->andWhere($builder->expr()->orX(
458
			// handle non empty folders separate
459
				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
460
				$builder->expr()->eq('f.size', new Literal(0))
461
			))
462
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
463
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
464
			->orderBy('f.mtime', 'DESC')
465
			->setMaxResults($limit)
466
			->setFirstResult($offset);
467
		return $query->execute()->fetchAll();
468
	}
469
470
	private function recentParse($result, $mountMap, $mimetypeLoader) {
471
		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
472
			$mount = $mountMap[$entry['storage']];
473
			$entry['internalPath'] = $entry['path'];
474
			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
475
			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
476
			$path = $this->getAbsolutePath($mount, $entry['path']);
477
			if (is_null($path)) {
478
				return null;
479
			}
480
			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
481
			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
482
		}, $result));
483
484
		return array_values(array_filter($files, function (Node $node) {
485
			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
486
			if (!$cacheEntry) {
487
				return false;
488
			}
489
			$relative = $this->getRelativePath($node->getPath());
490
			return $relative !== null && $relative !== '/'
491
				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
492
		}));
493
	}
494
495
	private function getAbsolutePath(IMountPoint $mount, $path) {
496
		$storage = $mount->getStorage();
497
		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
498
			if ($storage->instanceOfStorage(SharedStorage::class)) {
499
				$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

499
				$storage->/** @scrutinizer ignore-call */ 
500
              getSourceStorage();
Loading history...
500
			}
501
			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
502
			$jailRoot = $storage->getUnjailedPath('');
503
			$rootLength = strlen($jailRoot) + 1;
504
			if ($path === $jailRoot) {
505
				return $mount->getMountPoint();
506
			} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
507
				return $mount->getMountPoint() . substr($path, $rootLength);
508
			} else {
509
				return null;
510
			}
511
		} else {
512
			return $mount->getMountPoint() . $path;
513
		}
514
	}
515
}
516