Passed
Push — master ( 3c9a62...e6ef09 )
by Roeland
37:35 queued 24:27
created
lib/private/Files/Cache/Scanner.php 1 patch
Indentation   +487 added lines, -487 removed lines patch added patch discarded remove patch
@@ -56,491 +56,491 @@
 block discarded – undo
56 56
  * @package OC\Files\Cache
57 57
  */
58 58
 class Scanner extends BasicEmitter implements IScanner {
59
-	/**
60
-	 * @var \OC\Files\Storage\Storage $storage
61
-	 */
62
-	protected $storage;
63
-
64
-	/**
65
-	 * @var string $storageId
66
-	 */
67
-	protected $storageId;
68
-
69
-	/**
70
-	 * @var \OC\Files\Cache\Cache $cache
71
-	 */
72
-	protected $cache;
73
-
74
-	/**
75
-	 * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
76
-	 */
77
-	protected $cacheActive;
78
-
79
-	/**
80
-	 * @var bool $useTransactions whether to use transactions
81
-	 */
82
-	protected $useTransactions = true;
83
-
84
-	/**
85
-	 * @var \OCP\Lock\ILockingProvider
86
-	 */
87
-	protected $lockingProvider;
88
-
89
-	public function __construct(\OC\Files\Storage\Storage $storage) {
90
-		$this->storage = $storage;
91
-		$this->storageId = $this->storage->getId();
92
-		$this->cache = $storage->getCache();
93
-		$this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
94
-		$this->lockingProvider = \OC::$server->getLockingProvider();
95
-	}
96
-
97
-	/**
98
-	 * Whether to wrap the scanning of a folder in a database transaction
99
-	 * On default transactions are used
100
-	 *
101
-	 * @param bool $useTransactions
102
-	 */
103
-	public function setUseTransactions($useTransactions) {
104
-		$this->useTransactions = $useTransactions;
105
-	}
106
-
107
-	/**
108
-	 * get all the metadata of a file or folder
109
-	 * *
110
-	 *
111
-	 * @param string $path
112
-	 * @return array|null an array of metadata of the file
113
-	 */
114
-	protected function getData($path) {
115
-		$data = $this->storage->getMetaData($path);
116
-		if (is_null($data)) {
117
-			\OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
118
-		}
119
-		return $data;
120
-	}
121
-
122
-	/**
123
-	 * scan a single file and store it in the cache
124
-	 *
125
-	 * @param string $file
126
-	 * @param int $reuseExisting
127
-	 * @param int $parentId
128
-	 * @param array|null|false $cacheData existing data in the cache for the file to be scanned
129
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
130
-	 * @param null $data the metadata for the file, as returned by the storage
131
-	 * @return array|null an array of metadata of the scanned file
132
-	 * @throws \OCP\Lock\LockedException
133
-	 */
134
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
135
-		if ($file !== '') {
136
-			try {
137
-				$this->storage->verifyPath(dirname($file), basename($file));
138
-			} catch (\Exception $e) {
139
-				return null;
140
-			}
141
-		}
142
-		// only proceed if $file is not a partial file nor a blacklisted file
143
-		if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) {
144
-
145
-			//acquire a lock
146
-			if ($lock) {
147
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
148
-					$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
149
-				}
150
-			}
151
-
152
-			try {
153
-				$data = $data ?? $this->getData($file);
154
-			} catch (ForbiddenException $e) {
155
-				if ($lock) {
156
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
157
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
158
-					}
159
-				}
160
-
161
-				return null;
162
-			}
163
-
164
-			try {
165
-				if ($data) {
166
-
167
-					// pre-emit only if it was a file. By that we avoid counting/treating folders as files
168
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
169
-						$this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
170
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
171
-					}
172
-
173
-					$parent = dirname($file);
174
-					if ($parent === '.' or $parent === '/') {
175
-						$parent = '';
176
-					}
177
-					if ($parentId === -1) {
178
-						$parentId = $this->cache->getParentId($file);
179
-					}
180
-
181
-					// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
182
-					if ($file and $parentId === -1) {
183
-						$parentData = $this->scanFile($parent);
184
-						if (!$parentData) {
185
-							return null;
186
-						}
187
-						$parentId = $parentData['fileid'];
188
-					}
189
-					if ($parent) {
190
-						$data['parent'] = $parentId;
191
-					}
192
-					if (is_null($cacheData)) {
193
-						/** @var CacheEntry $cacheData */
194
-						$cacheData = $this->cache->get($file);
195
-					}
196
-					if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
197
-						// prevent empty etag
198
-						if (empty($cacheData['etag'])) {
199
-							$etag = $data['etag'];
200
-						} else {
201
-							$etag = $cacheData['etag'];
202
-						}
203
-						$fileId = $cacheData['fileid'];
204
-						$data['fileid'] = $fileId;
205
-						// only reuse data if the file hasn't explicitly changed
206
-						if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
207
-							$data['mtime'] = $cacheData['mtime'];
208
-							if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
209
-								$data['size'] = $cacheData['size'];
210
-							}
211
-							if ($reuseExisting & self::REUSE_ETAG) {
212
-								$data['etag'] = $etag;
213
-							}
214
-						}
215
-						// Only update metadata that has changed
216
-						$newData = array_diff_assoc($data, $cacheData->getData());
217
-					} else {
218
-						$newData = $data;
219
-						$fileId = -1;
220
-					}
221
-					if (!empty($newData)) {
222
-						// Reset the checksum if the data has changed
223
-						$newData['checksum'] = '';
224
-						$newData['parent'] = $parentId;
225
-						$data['fileid'] = $this->addToCache($file, $newData, $fileId);
226
-					}
227
-					if ($cacheData && isset($cacheData['size'])) {
228
-						$data['oldSize'] = $cacheData['size'];
229
-					} else {
230
-						$data['oldSize'] = 0;
231
-					}
232
-
233
-					if ($cacheData && isset($cacheData['encrypted'])) {
234
-						$data['encrypted'] = $cacheData['encrypted'];
235
-					}
236
-
237
-					// post-emit only if it was a file. By that we avoid counting/treating folders as files
238
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
239
-						$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
240
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
241
-					}
242
-				} else {
243
-					$this->removeFromCache($file);
244
-				}
245
-			} catch (\Exception $e) {
246
-				if ($lock) {
247
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
248
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
249
-					}
250
-				}
251
-				throw $e;
252
-			}
253
-
254
-			//release the acquired lock
255
-			if ($lock) {
256
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
257
-					$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
258
-				}
259
-			}
260
-
261
-			if ($data && !isset($data['encrypted'])) {
262
-				$data['encrypted'] = false;
263
-			}
264
-			return $data;
265
-		}
266
-
267
-		return null;
268
-	}
269
-
270
-	protected function removeFromCache($path) {
271
-		\OC_Hook::emit('Scanner', 'removeFromCache', ['file' => $path]);
272
-		$this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', [$path]);
273
-		if ($this->cacheActive) {
274
-			$this->cache->remove($path);
275
-		}
276
-	}
277
-
278
-	/**
279
-	 * @param string $path
280
-	 * @param array $data
281
-	 * @param int $fileId
282
-	 * @return int the id of the added file
283
-	 */
284
-	protected function addToCache($path, $data, $fileId = -1) {
285
-		if (isset($data['scan_permissions'])) {
286
-			$data['permissions'] = $data['scan_permissions'];
287
-		}
288
-		\OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
289
-		$this->emit('\OC\Files\Cache\Scanner', 'addToCache', [$path, $this->storageId, $data]);
290
-		if ($this->cacheActive) {
291
-			if ($fileId !== -1) {
292
-				$this->cache->update($fileId, $data);
293
-				return $fileId;
294
-			} else {
295
-				return $this->cache->insert($path, $data);
296
-			}
297
-		} else {
298
-			return -1;
299
-		}
300
-	}
301
-
302
-	/**
303
-	 * @param string $path
304
-	 * @param array $data
305
-	 * @param int $fileId
306
-	 */
307
-	protected function updateCache($path, $data, $fileId = -1) {
308
-		\OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
309
-		$this->emit('\OC\Files\Cache\Scanner', 'updateCache', [$path, $this->storageId, $data]);
310
-		if ($this->cacheActive) {
311
-			if ($fileId !== -1) {
312
-				$this->cache->update($fileId, $data);
313
-			} else {
314
-				$this->cache->put($path, $data);
315
-			}
316
-		}
317
-	}
318
-
319
-	/**
320
-	 * scan a folder and all it's children
321
-	 *
322
-	 * @param string $path
323
-	 * @param bool $recursive
324
-	 * @param int $reuse
325
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
326
-	 * @return array|null an array of the meta data of the scanned file or folder
327
-	 */
328
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
329
-		if ($reuse === -1) {
330
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
331
-		}
332
-		if ($lock) {
333
-			if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
334
-				$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
335
-				$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
336
-			}
337
-		}
338
-		try {
339
-			$data = $this->scanFile($path, $reuse, -1, null, $lock);
340
-			if ($data and $data['mimetype'] === 'httpd/unix-directory') {
341
-				$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
342
-				$data['size'] = $size;
343
-			}
344
-		} finally {
345
-			if ($lock) {
346
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
347
-					$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
348
-					$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
349
-				}
350
-			}
351
-		}
352
-		return $data;
353
-	}
354
-
355
-	/**
356
-	 * Get the children currently in the cache
357
-	 *
358
-	 * @param int $folderId
359
-	 * @return array[]
360
-	 */
361
-	protected function getExistingChildren($folderId) {
362
-		$existingChildren = [];
363
-		$children = $this->cache->getFolderContentsById($folderId);
364
-		foreach ($children as $child) {
365
-			$existingChildren[$child['name']] = $child;
366
-		}
367
-		return $existingChildren;
368
-	}
369
-
370
-	/**
371
-	 * scan all the files and folders in a folder
372
-	 *
373
-	 * @param string $path
374
-	 * @param bool $recursive
375
-	 * @param int $reuse
376
-	 * @param int $folderId id for the folder to be scanned
377
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
378
-	 * @return int the size of the scanned folder or -1 if the size is unknown at this stage
379
-	 */
380
-	protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
381
-		if ($reuse === -1) {
382
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
383
-		}
384
-		$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
385
-		$size = 0;
386
-		if (!is_null($folderId)) {
387
-			$folderId = $this->cache->getId($path);
388
-		}
389
-		$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
390
-
391
-		foreach ($childQueue as $child => $childId) {
392
-			$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
393
-			if ($childSize === -1) {
394
-				$size = -1;
395
-			} elseif ($size !== -1) {
396
-				$size += $childSize;
397
-			}
398
-		}
399
-		if ($this->cacheActive) {
400
-			$this->cache->update($folderId, ['size' => $size]);
401
-		}
402
-		$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
403
-		return $size;
404
-	}
405
-
406
-	private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
407
-		// we put this in it's own function so it cleans up the memory before we start recursing
408
-		$existingChildren = $this->getExistingChildren($folderId);
409
-		$newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
410
-
411
-		if ($this->useTransactions) {
412
-			\OC::$server->getDatabaseConnection()->beginTransaction();
413
-		}
414
-
415
-		$exceptionOccurred = false;
416
-		$childQueue = [];
417
-		$newChildNames = [];
418
-		foreach ($newChildren as $fileMeta) {
419
-			$permissions = isset($fileMeta['scan_permissions']) ? $fileMeta['scan_permissions'] : $fileMeta['permissions'];
420
-			if ($permissions === 0) {
421
-				continue;
422
-			}
423
-			$file = $fileMeta['name'];
424
-			$newChildNames[] = $file;
425
-			$child = $path ? $path . '/' . $file : $file;
426
-			try {
427
-				$existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false;
428
-				$data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta);
429
-				if ($data) {
430
-					if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
431
-						$childQueue[$child] = $data['fileid'];
432
-					} elseif ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
433
-						// only recurse into folders which aren't fully scanned
434
-						$childQueue[$child] = $data['fileid'];
435
-					} elseif ($data['size'] === -1) {
436
-						$size = -1;
437
-					} elseif ($size !== -1) {
438
-						$size += $data['size'];
439
-					}
440
-				}
441
-			} catch (Exception $ex) {
442
-				// might happen if inserting duplicate while a scanning
443
-				// process is running in parallel
444
-				// log and ignore
445
-				if ($this->useTransactions) {
446
-					\OC::$server->getDatabaseConnection()->rollback();
447
-					\OC::$server->getDatabaseConnection()->beginTransaction();
448
-				}
449
-				\OC::$server->getLogger()->logException($ex, [
450
-					'message' => 'Exception while scanning file "' . $child . '"',
451
-					'level' => ILogger::DEBUG,
452
-					'app' => 'core',
453
-				]);
454
-				$exceptionOccurred = true;
455
-			} catch (\OCP\Lock\LockedException $e) {
456
-				if ($this->useTransactions) {
457
-					\OC::$server->getDatabaseConnection()->rollback();
458
-				}
459
-				throw $e;
460
-			}
461
-		}
462
-		$removedChildren = \array_diff(array_keys($existingChildren), $newChildNames);
463
-		foreach ($removedChildren as $childName) {
464
-			$child = $path ? $path . '/' . $childName : $childName;
465
-			$this->removeFromCache($child);
466
-		}
467
-		if ($this->useTransactions) {
468
-			\OC::$server->getDatabaseConnection()->commit();
469
-		}
470
-		if ($exceptionOccurred) {
471
-			// It might happen that the parallel scan process has already
472
-			// inserted mimetypes but those weren't available yet inside the transaction
473
-			// To make sure to have the updated mime types in such cases,
474
-			// we reload them here
475
-			\OC::$server->getMimeTypeLoader()->reset();
476
-		}
477
-		return $childQueue;
478
-	}
479
-
480
-	/**
481
-	 * check if the file should be ignored when scanning
482
-	 * NOTE: files with a '.part' extension are ignored as well!
483
-	 *       prevents unfinished put requests to be scanned
484
-	 *
485
-	 * @param string $file
486
-	 * @return boolean
487
-	 */
488
-	public static function isPartialFile($file) {
489
-		if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
490
-			return true;
491
-		}
492
-		if (strpos($file, '.part/') !== false) {
493
-			return true;
494
-		}
495
-
496
-		return false;
497
-	}
498
-
499
-	/**
500
-	 * walk over any folders that are not fully scanned yet and scan them
501
-	 */
502
-	public function backgroundScan() {
503
-		if (!$this->cache->inCache('')) {
504
-			$this->runBackgroundScanJob(function () {
505
-				$this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
506
-			}, '');
507
-		} else {
508
-			$lastPath = null;
509
-			while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
510
-				$this->runBackgroundScanJob(function () use ($path) {
511
-					$this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
512
-				}, $path);
513
-				// FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
514
-				// to make this possible
515
-				$lastPath = $path;
516
-			}
517
-		}
518
-	}
519
-
520
-	private function runBackgroundScanJob(callable $callback, $path) {
521
-		try {
522
-			$callback();
523
-			\OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]);
524
-			if ($this->cacheActive && $this->cache instanceof Cache) {
525
-				$this->cache->correctFolderSize($path, null, true);
526
-			}
527
-		} catch (\OCP\Files\StorageInvalidException $e) {
528
-			// skip unavailable storages
529
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
530
-			// skip unavailable storages
531
-		} catch (\OCP\Files\ForbiddenException $e) {
532
-			// skip forbidden storages
533
-		} catch (\OCP\Lock\LockedException $e) {
534
-			// skip unavailable storages
535
-		}
536
-	}
537
-
538
-	/**
539
-	 * Set whether the cache is affected by scan operations
540
-	 *
541
-	 * @param boolean $active The active state of the cache
542
-	 */
543
-	public function setCacheActive($active) {
544
-		$this->cacheActive = $active;
545
-	}
59
+    /**
60
+     * @var \OC\Files\Storage\Storage $storage
61
+     */
62
+    protected $storage;
63
+
64
+    /**
65
+     * @var string $storageId
66
+     */
67
+    protected $storageId;
68
+
69
+    /**
70
+     * @var \OC\Files\Cache\Cache $cache
71
+     */
72
+    protected $cache;
73
+
74
+    /**
75
+     * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
76
+     */
77
+    protected $cacheActive;
78
+
79
+    /**
80
+     * @var bool $useTransactions whether to use transactions
81
+     */
82
+    protected $useTransactions = true;
83
+
84
+    /**
85
+     * @var \OCP\Lock\ILockingProvider
86
+     */
87
+    protected $lockingProvider;
88
+
89
+    public function __construct(\OC\Files\Storage\Storage $storage) {
90
+        $this->storage = $storage;
91
+        $this->storageId = $this->storage->getId();
92
+        $this->cache = $storage->getCache();
93
+        $this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
94
+        $this->lockingProvider = \OC::$server->getLockingProvider();
95
+    }
96
+
97
+    /**
98
+     * Whether to wrap the scanning of a folder in a database transaction
99
+     * On default transactions are used
100
+     *
101
+     * @param bool $useTransactions
102
+     */
103
+    public function setUseTransactions($useTransactions) {
104
+        $this->useTransactions = $useTransactions;
105
+    }
106
+
107
+    /**
108
+     * get all the metadata of a file or folder
109
+     * *
110
+     *
111
+     * @param string $path
112
+     * @return array|null an array of metadata of the file
113
+     */
114
+    protected function getData($path) {
115
+        $data = $this->storage->getMetaData($path);
116
+        if (is_null($data)) {
117
+            \OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
118
+        }
119
+        return $data;
120
+    }
121
+
122
+    /**
123
+     * scan a single file and store it in the cache
124
+     *
125
+     * @param string $file
126
+     * @param int $reuseExisting
127
+     * @param int $parentId
128
+     * @param array|null|false $cacheData existing data in the cache for the file to be scanned
129
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
130
+     * @param null $data the metadata for the file, as returned by the storage
131
+     * @return array|null an array of metadata of the scanned file
132
+     * @throws \OCP\Lock\LockedException
133
+     */
134
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
135
+        if ($file !== '') {
136
+            try {
137
+                $this->storage->verifyPath(dirname($file), basename($file));
138
+            } catch (\Exception $e) {
139
+                return null;
140
+            }
141
+        }
142
+        // only proceed if $file is not a partial file nor a blacklisted file
143
+        if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) {
144
+
145
+            //acquire a lock
146
+            if ($lock) {
147
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
148
+                    $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
149
+                }
150
+            }
151
+
152
+            try {
153
+                $data = $data ?? $this->getData($file);
154
+            } catch (ForbiddenException $e) {
155
+                if ($lock) {
156
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
157
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
158
+                    }
159
+                }
160
+
161
+                return null;
162
+            }
163
+
164
+            try {
165
+                if ($data) {
166
+
167
+                    // pre-emit only if it was a file. By that we avoid counting/treating folders as files
168
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
169
+                        $this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
170
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
171
+                    }
172
+
173
+                    $parent = dirname($file);
174
+                    if ($parent === '.' or $parent === '/') {
175
+                        $parent = '';
176
+                    }
177
+                    if ($parentId === -1) {
178
+                        $parentId = $this->cache->getParentId($file);
179
+                    }
180
+
181
+                    // scan the parent if it's not in the cache (id -1) and the current file is not the root folder
182
+                    if ($file and $parentId === -1) {
183
+                        $parentData = $this->scanFile($parent);
184
+                        if (!$parentData) {
185
+                            return null;
186
+                        }
187
+                        $parentId = $parentData['fileid'];
188
+                    }
189
+                    if ($parent) {
190
+                        $data['parent'] = $parentId;
191
+                    }
192
+                    if (is_null($cacheData)) {
193
+                        /** @var CacheEntry $cacheData */
194
+                        $cacheData = $this->cache->get($file);
195
+                    }
196
+                    if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
197
+                        // prevent empty etag
198
+                        if (empty($cacheData['etag'])) {
199
+                            $etag = $data['etag'];
200
+                        } else {
201
+                            $etag = $cacheData['etag'];
202
+                        }
203
+                        $fileId = $cacheData['fileid'];
204
+                        $data['fileid'] = $fileId;
205
+                        // only reuse data if the file hasn't explicitly changed
206
+                        if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
207
+                            $data['mtime'] = $cacheData['mtime'];
208
+                            if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
209
+                                $data['size'] = $cacheData['size'];
210
+                            }
211
+                            if ($reuseExisting & self::REUSE_ETAG) {
212
+                                $data['etag'] = $etag;
213
+                            }
214
+                        }
215
+                        // Only update metadata that has changed
216
+                        $newData = array_diff_assoc($data, $cacheData->getData());
217
+                    } else {
218
+                        $newData = $data;
219
+                        $fileId = -1;
220
+                    }
221
+                    if (!empty($newData)) {
222
+                        // Reset the checksum if the data has changed
223
+                        $newData['checksum'] = '';
224
+                        $newData['parent'] = $parentId;
225
+                        $data['fileid'] = $this->addToCache($file, $newData, $fileId);
226
+                    }
227
+                    if ($cacheData && isset($cacheData['size'])) {
228
+                        $data['oldSize'] = $cacheData['size'];
229
+                    } else {
230
+                        $data['oldSize'] = 0;
231
+                    }
232
+
233
+                    if ($cacheData && isset($cacheData['encrypted'])) {
234
+                        $data['encrypted'] = $cacheData['encrypted'];
235
+                    }
236
+
237
+                    // post-emit only if it was a file. By that we avoid counting/treating folders as files
238
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
239
+                        $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
240
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
241
+                    }
242
+                } else {
243
+                    $this->removeFromCache($file);
244
+                }
245
+            } catch (\Exception $e) {
246
+                if ($lock) {
247
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
248
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
249
+                    }
250
+                }
251
+                throw $e;
252
+            }
253
+
254
+            //release the acquired lock
255
+            if ($lock) {
256
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
257
+                    $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
258
+                }
259
+            }
260
+
261
+            if ($data && !isset($data['encrypted'])) {
262
+                $data['encrypted'] = false;
263
+            }
264
+            return $data;
265
+        }
266
+
267
+        return null;
268
+    }
269
+
270
+    protected function removeFromCache($path) {
271
+        \OC_Hook::emit('Scanner', 'removeFromCache', ['file' => $path]);
272
+        $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', [$path]);
273
+        if ($this->cacheActive) {
274
+            $this->cache->remove($path);
275
+        }
276
+    }
277
+
278
+    /**
279
+     * @param string $path
280
+     * @param array $data
281
+     * @param int $fileId
282
+     * @return int the id of the added file
283
+     */
284
+    protected function addToCache($path, $data, $fileId = -1) {
285
+        if (isset($data['scan_permissions'])) {
286
+            $data['permissions'] = $data['scan_permissions'];
287
+        }
288
+        \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
289
+        $this->emit('\OC\Files\Cache\Scanner', 'addToCache', [$path, $this->storageId, $data]);
290
+        if ($this->cacheActive) {
291
+            if ($fileId !== -1) {
292
+                $this->cache->update($fileId, $data);
293
+                return $fileId;
294
+            } else {
295
+                return $this->cache->insert($path, $data);
296
+            }
297
+        } else {
298
+            return -1;
299
+        }
300
+    }
301
+
302
+    /**
303
+     * @param string $path
304
+     * @param array $data
305
+     * @param int $fileId
306
+     */
307
+    protected function updateCache($path, $data, $fileId = -1) {
308
+        \OC_Hook::emit('Scanner', 'addToCache', ['file' => $path, 'data' => $data]);
309
+        $this->emit('\OC\Files\Cache\Scanner', 'updateCache', [$path, $this->storageId, $data]);
310
+        if ($this->cacheActive) {
311
+            if ($fileId !== -1) {
312
+                $this->cache->update($fileId, $data);
313
+            } else {
314
+                $this->cache->put($path, $data);
315
+            }
316
+        }
317
+    }
318
+
319
+    /**
320
+     * scan a folder and all it's children
321
+     *
322
+     * @param string $path
323
+     * @param bool $recursive
324
+     * @param int $reuse
325
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
326
+     * @return array|null an array of the meta data of the scanned file or folder
327
+     */
328
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
329
+        if ($reuse === -1) {
330
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
331
+        }
332
+        if ($lock) {
333
+            if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
334
+                $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
335
+                $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
336
+            }
337
+        }
338
+        try {
339
+            $data = $this->scanFile($path, $reuse, -1, null, $lock);
340
+            if ($data and $data['mimetype'] === 'httpd/unix-directory') {
341
+                $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
342
+                $data['size'] = $size;
343
+            }
344
+        } finally {
345
+            if ($lock) {
346
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
347
+                    $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
348
+                    $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
349
+                }
350
+            }
351
+        }
352
+        return $data;
353
+    }
354
+
355
+    /**
356
+     * Get the children currently in the cache
357
+     *
358
+     * @param int $folderId
359
+     * @return array[]
360
+     */
361
+    protected function getExistingChildren($folderId) {
362
+        $existingChildren = [];
363
+        $children = $this->cache->getFolderContentsById($folderId);
364
+        foreach ($children as $child) {
365
+            $existingChildren[$child['name']] = $child;
366
+        }
367
+        return $existingChildren;
368
+    }
369
+
370
+    /**
371
+     * scan all the files and folders in a folder
372
+     *
373
+     * @param string $path
374
+     * @param bool $recursive
375
+     * @param int $reuse
376
+     * @param int $folderId id for the folder to be scanned
377
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
378
+     * @return int the size of the scanned folder or -1 if the size is unknown at this stage
379
+     */
380
+    protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
381
+        if ($reuse === -1) {
382
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
383
+        }
384
+        $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
385
+        $size = 0;
386
+        if (!is_null($folderId)) {
387
+            $folderId = $this->cache->getId($path);
388
+        }
389
+        $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
390
+
391
+        foreach ($childQueue as $child => $childId) {
392
+            $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
393
+            if ($childSize === -1) {
394
+                $size = -1;
395
+            } elseif ($size !== -1) {
396
+                $size += $childSize;
397
+            }
398
+        }
399
+        if ($this->cacheActive) {
400
+            $this->cache->update($folderId, ['size' => $size]);
401
+        }
402
+        $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
403
+        return $size;
404
+    }
405
+
406
+    private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
407
+        // we put this in it's own function so it cleans up the memory before we start recursing
408
+        $existingChildren = $this->getExistingChildren($folderId);
409
+        $newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
410
+
411
+        if ($this->useTransactions) {
412
+            \OC::$server->getDatabaseConnection()->beginTransaction();
413
+        }
414
+
415
+        $exceptionOccurred = false;
416
+        $childQueue = [];
417
+        $newChildNames = [];
418
+        foreach ($newChildren as $fileMeta) {
419
+            $permissions = isset($fileMeta['scan_permissions']) ? $fileMeta['scan_permissions'] : $fileMeta['permissions'];
420
+            if ($permissions === 0) {
421
+                continue;
422
+            }
423
+            $file = $fileMeta['name'];
424
+            $newChildNames[] = $file;
425
+            $child = $path ? $path . '/' . $file : $file;
426
+            try {
427
+                $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false;
428
+                $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta);
429
+                if ($data) {
430
+                    if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
431
+                        $childQueue[$child] = $data['fileid'];
432
+                    } elseif ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
433
+                        // only recurse into folders which aren't fully scanned
434
+                        $childQueue[$child] = $data['fileid'];
435
+                    } elseif ($data['size'] === -1) {
436
+                        $size = -1;
437
+                    } elseif ($size !== -1) {
438
+                        $size += $data['size'];
439
+                    }
440
+                }
441
+            } catch (Exception $ex) {
442
+                // might happen if inserting duplicate while a scanning
443
+                // process is running in parallel
444
+                // log and ignore
445
+                if ($this->useTransactions) {
446
+                    \OC::$server->getDatabaseConnection()->rollback();
447
+                    \OC::$server->getDatabaseConnection()->beginTransaction();
448
+                }
449
+                \OC::$server->getLogger()->logException($ex, [
450
+                    'message' => 'Exception while scanning file "' . $child . '"',
451
+                    'level' => ILogger::DEBUG,
452
+                    'app' => 'core',
453
+                ]);
454
+                $exceptionOccurred = true;
455
+            } catch (\OCP\Lock\LockedException $e) {
456
+                if ($this->useTransactions) {
457
+                    \OC::$server->getDatabaseConnection()->rollback();
458
+                }
459
+                throw $e;
460
+            }
461
+        }
462
+        $removedChildren = \array_diff(array_keys($existingChildren), $newChildNames);
463
+        foreach ($removedChildren as $childName) {
464
+            $child = $path ? $path . '/' . $childName : $childName;
465
+            $this->removeFromCache($child);
466
+        }
467
+        if ($this->useTransactions) {
468
+            \OC::$server->getDatabaseConnection()->commit();
469
+        }
470
+        if ($exceptionOccurred) {
471
+            // It might happen that the parallel scan process has already
472
+            // inserted mimetypes but those weren't available yet inside the transaction
473
+            // To make sure to have the updated mime types in such cases,
474
+            // we reload them here
475
+            \OC::$server->getMimeTypeLoader()->reset();
476
+        }
477
+        return $childQueue;
478
+    }
479
+
480
+    /**
481
+     * check if the file should be ignored when scanning
482
+     * NOTE: files with a '.part' extension are ignored as well!
483
+     *       prevents unfinished put requests to be scanned
484
+     *
485
+     * @param string $file
486
+     * @return boolean
487
+     */
488
+    public static function isPartialFile($file) {
489
+        if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
490
+            return true;
491
+        }
492
+        if (strpos($file, '.part/') !== false) {
493
+            return true;
494
+        }
495
+
496
+        return false;
497
+    }
498
+
499
+    /**
500
+     * walk over any folders that are not fully scanned yet and scan them
501
+     */
502
+    public function backgroundScan() {
503
+        if (!$this->cache->inCache('')) {
504
+            $this->runBackgroundScanJob(function () {
505
+                $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
506
+            }, '');
507
+        } else {
508
+            $lastPath = null;
509
+            while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
510
+                $this->runBackgroundScanJob(function () use ($path) {
511
+                    $this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
512
+                }, $path);
513
+                // FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
514
+                // to make this possible
515
+                $lastPath = $path;
516
+            }
517
+        }
518
+    }
519
+
520
+    private function runBackgroundScanJob(callable $callback, $path) {
521
+        try {
522
+            $callback();
523
+            \OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]);
524
+            if ($this->cacheActive && $this->cache instanceof Cache) {
525
+                $this->cache->correctFolderSize($path, null, true);
526
+            }
527
+        } catch (\OCP\Files\StorageInvalidException $e) {
528
+            // skip unavailable storages
529
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
530
+            // skip unavailable storages
531
+        } catch (\OCP\Files\ForbiddenException $e) {
532
+            // skip forbidden storages
533
+        } catch (\OCP\Lock\LockedException $e) {
534
+            // skip unavailable storages
535
+        }
536
+    }
537
+
538
+    /**
539
+     * Set whether the cache is affected by scan operations
540
+     *
541
+     * @param boolean $active The active state of the cache
542
+     */
543
+    public function setCacheActive($active) {
544
+        $this->cacheActive = $active;
545
+    }
546 546
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Storage.php 1 patch
Indentation   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -34,108 +34,108 @@
 block discarded – undo
34 34
  */
35 35
 interface Storage extends \OCP\Files\Storage {
36 36
 
37
-	/**
38
-	 * get a cache instance for the storage
39
-	 *
40
-	 * @param string $path
41
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
42
-	 * @return \OC\Files\Cache\Cache
43
-	 */
44
-	public function getCache($path = '', $storage = null);
45
-
46
-	/**
47
-	 * get a scanner instance for the storage
48
-	 *
49
-	 * @param string $path
50
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
51
-	 * @return \OC\Files\Cache\Scanner
52
-	 */
53
-	public function getScanner($path = '', $storage = null);
54
-
55
-
56
-	/**
57
-	 * get the user id of the owner of a file or folder
58
-	 *
59
-	 * @param string $path
60
-	 * @return string
61
-	 */
62
-	public function getOwner($path);
63
-
64
-	/**
65
-	 * get a watcher instance for the cache
66
-	 *
67
-	 * @param string $path
68
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
69
-	 * @return \OC\Files\Cache\Watcher
70
-	 */
71
-	public function getWatcher($path = '', $storage = null);
72
-
73
-	/**
74
-	 * get a propagator instance for the cache
75
-	 *
76
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
77
-	 * @return \OC\Files\Cache\Propagator
78
-	 */
79
-	public function getPropagator($storage = null);
80
-
81
-	/**
82
-	 * get a updater instance for the cache
83
-	 *
84
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
85
-	 * @return \OC\Files\Cache\Updater
86
-	 */
87
-	public function getUpdater($storage = null);
88
-
89
-	/**
90
-	 * @return \OC\Files\Cache\Storage
91
-	 */
92
-	public function getStorageCache();
93
-
94
-	/**
95
-	 * @param string $path
96
-	 * @return array|null
97
-	 */
98
-	public function getMetaData($path);
99
-
100
-	/**
101
-	 * @param string $path The path of the file to acquire the lock for
102
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
103
-	 * @param \OCP\Lock\ILockingProvider $provider
104
-	 * @throws \OCP\Lock\LockedException
105
-	 */
106
-	public function acquireLock($path, $type, ILockingProvider $provider);
107
-
108
-	/**
109
-	 * @param string $path The path of the file to release the lock for
110
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
111
-	 * @param \OCP\Lock\ILockingProvider $provider
112
-	 * @throws \OCP\Lock\LockedException
113
-	 */
114
-	public function releaseLock($path, $type, ILockingProvider $provider);
115
-
116
-	/**
117
-	 * @param string $path The path of the file to change the lock for
118
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
119
-	 * @param \OCP\Lock\ILockingProvider $provider
120
-	 * @throws \OCP\Lock\LockedException
121
-	 */
122
-	public function changeLock($path, $type, ILockingProvider $provider);
123
-
124
-	/**
125
-	 * Get the contents of a directory with metadata
126
-	 *
127
-	 * @param string $directory
128
-	 * @return \Traversable an iterator, containing file metadata
129
-	 *
130
-	 * The metadata array will contain the following fields
131
-	 *
132
-	 * - name
133
-	 * - mimetype
134
-	 * - mtime
135
-	 * - size
136
-	 * - etag
137
-	 * - storage_mtime
138
-	 * - permissions
139
-	 */
140
-	public function getDirectoryContent($directory): \Traversable;
37
+    /**
38
+     * get a cache instance for the storage
39
+     *
40
+     * @param string $path
41
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
42
+     * @return \OC\Files\Cache\Cache
43
+     */
44
+    public function getCache($path = '', $storage = null);
45
+
46
+    /**
47
+     * get a scanner instance for the storage
48
+     *
49
+     * @param string $path
50
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
51
+     * @return \OC\Files\Cache\Scanner
52
+     */
53
+    public function getScanner($path = '', $storage = null);
54
+
55
+
56
+    /**
57
+     * get the user id of the owner of a file or folder
58
+     *
59
+     * @param string $path
60
+     * @return string
61
+     */
62
+    public function getOwner($path);
63
+
64
+    /**
65
+     * get a watcher instance for the cache
66
+     *
67
+     * @param string $path
68
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
69
+     * @return \OC\Files\Cache\Watcher
70
+     */
71
+    public function getWatcher($path = '', $storage = null);
72
+
73
+    /**
74
+     * get a propagator instance for the cache
75
+     *
76
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
77
+     * @return \OC\Files\Cache\Propagator
78
+     */
79
+    public function getPropagator($storage = null);
80
+
81
+    /**
82
+     * get a updater instance for the cache
83
+     *
84
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
85
+     * @return \OC\Files\Cache\Updater
86
+     */
87
+    public function getUpdater($storage = null);
88
+
89
+    /**
90
+     * @return \OC\Files\Cache\Storage
91
+     */
92
+    public function getStorageCache();
93
+
94
+    /**
95
+     * @param string $path
96
+     * @return array|null
97
+     */
98
+    public function getMetaData($path);
99
+
100
+    /**
101
+     * @param string $path The path of the file to acquire the lock for
102
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
103
+     * @param \OCP\Lock\ILockingProvider $provider
104
+     * @throws \OCP\Lock\LockedException
105
+     */
106
+    public function acquireLock($path, $type, ILockingProvider $provider);
107
+
108
+    /**
109
+     * @param string $path The path of the file to release the lock for
110
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
111
+     * @param \OCP\Lock\ILockingProvider $provider
112
+     * @throws \OCP\Lock\LockedException
113
+     */
114
+    public function releaseLock($path, $type, ILockingProvider $provider);
115
+
116
+    /**
117
+     * @param string $path The path of the file to change the lock for
118
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
119
+     * @param \OCP\Lock\ILockingProvider $provider
120
+     * @throws \OCP\Lock\LockedException
121
+     */
122
+    public function changeLock($path, $type, ILockingProvider $provider);
123
+
124
+    /**
125
+     * Get the contents of a directory with metadata
126
+     *
127
+     * @param string $directory
128
+     * @return \Traversable an iterator, containing file metadata
129
+     *
130
+     * The metadata array will contain the following fields
131
+     *
132
+     * - name
133
+     * - mimetype
134
+     * - mtime
135
+     * - size
136
+     * - etag
137
+     * - storage_mtime
138
+     * - permissions
139
+     */
140
+    public function getDirectoryContent($directory): \Traversable;
141 141
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encryption.php 1 patch
Indentation   +984 added lines, -984 removed lines patch added patch discarded remove patch
@@ -53,988 +53,988 @@
 block discarded – undo
53 53
 use OCP\ILogger;
54 54
 
55 55
 class Encryption extends Wrapper {
56
-	use LocalTempFileTrait;
57
-
58
-	/** @var string */
59
-	private $mountPoint;
60
-
61
-	/** @var \OC\Encryption\Util */
62
-	private $util;
63
-
64
-	/** @var \OCP\Encryption\IManager */
65
-	private $encryptionManager;
66
-
67
-	/** @var \OCP\ILogger */
68
-	private $logger;
69
-
70
-	/** @var string */
71
-	private $uid;
72
-
73
-	/** @var array */
74
-	protected $unencryptedSize;
75
-
76
-	/** @var \OCP\Encryption\IFile */
77
-	private $fileHelper;
78
-
79
-	/** @var IMountPoint */
80
-	private $mount;
81
-
82
-	/** @var IStorage */
83
-	private $keyStorage;
84
-
85
-	/** @var Update */
86
-	private $update;
87
-
88
-	/** @var Manager */
89
-	private $mountManager;
90
-
91
-	/** @var array remember for which path we execute the repair step to avoid recursions */
92
-	private $fixUnencryptedSizeOf = [];
93
-
94
-	/** @var  ArrayCache */
95
-	private $arrayCache;
96
-
97
-	/**
98
-	 * @param array $parameters
99
-	 * @param IManager $encryptionManager
100
-	 * @param Util $util
101
-	 * @param ILogger $logger
102
-	 * @param IFile $fileHelper
103
-	 * @param string $uid
104
-	 * @param IStorage $keyStorage
105
-	 * @param Update $update
106
-	 * @param Manager $mountManager
107
-	 * @param ArrayCache $arrayCache
108
-	 */
109
-	public function __construct(
110
-		$parameters,
111
-		IManager $encryptionManager = null,
112
-		Util $util = null,
113
-		ILogger $logger = null,
114
-		IFile $fileHelper = null,
115
-		$uid = null,
116
-		IStorage $keyStorage = null,
117
-		Update $update = null,
118
-		Manager $mountManager = null,
119
-		ArrayCache $arrayCache = null
120
-	) {
121
-		$this->mountPoint = $parameters['mountPoint'];
122
-		$this->mount = $parameters['mount'];
123
-		$this->encryptionManager = $encryptionManager;
124
-		$this->util = $util;
125
-		$this->logger = $logger;
126
-		$this->uid = $uid;
127
-		$this->fileHelper = $fileHelper;
128
-		$this->keyStorage = $keyStorage;
129
-		$this->unencryptedSize = [];
130
-		$this->update = $update;
131
-		$this->mountManager = $mountManager;
132
-		$this->arrayCache = $arrayCache;
133
-		parent::__construct($parameters);
134
-	}
135
-
136
-	/**
137
-	 * see https://www.php.net/manual/en/function.filesize.php
138
-	 * The result for filesize when called on a folder is required to be 0
139
-	 *
140
-	 * @param string $path
141
-	 * @return int
142
-	 */
143
-	public function filesize($path) {
144
-		$fullPath = $this->getFullPath($path);
145
-
146
-		/** @var CacheEntry $info */
147
-		$info = $this->getCache()->get($path);
148
-		if (isset($this->unencryptedSize[$fullPath])) {
149
-			$size = $this->unencryptedSize[$fullPath];
150
-			// update file cache
151
-			if ($info instanceof ICacheEntry) {
152
-				$info = $info->getData();
153
-				$info['encrypted'] = $info['encryptedVersion'];
154
-			} else {
155
-				if (!is_array($info)) {
156
-					$info = [];
157
-				}
158
-				$info['encrypted'] = true;
159
-			}
160
-
161
-			$info['size'] = $size;
162
-			$this->getCache()->put($path, $info);
163
-
164
-			return $size;
165
-		}
166
-
167
-		if (isset($info['fileid']) && $info['encrypted']) {
168
-			return $this->verifyUnencryptedSize($path, $info['size']);
169
-		}
170
-
171
-		return $this->storage->filesize($path);
172
-	}
173
-
174
-	private function modifyMetaData(string $path, array $data): array {
175
-		$fullPath = $this->getFullPath($path);
176
-		$info = $this->getCache()->get($path);
177
-
178
-		if (isset($this->unencryptedSize[$fullPath])) {
179
-			$data['encrypted'] = true;
180
-			$data['size'] = $this->unencryptedSize[$fullPath];
181
-		} else {
182
-			if (isset($info['fileid']) && $info['encrypted']) {
183
-				$data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
184
-				$data['encrypted'] = true;
185
-			}
186
-		}
187
-
188
-		if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
189
-			$data['encryptedVersion'] = $info['encryptedVersion'];
190
-		}
191
-
192
-		return $data;
193
-	}
194
-
195
-	public function getMetaData($path) {
196
-		$data = $this->storage->getMetaData($path);
197
-		if (is_null($data)) {
198
-			return null;
199
-		}
200
-		return $this->modifyMetaData($path, $data);
201
-	}
202
-
203
-	public function getDirectoryContent($directory): \Traversable {
204
-		$parent = rtrim($directory, '/');
205
-		foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
206
-			yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
207
-		}
208
-	}
209
-
210
-	/**
211
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
212
-	 *
213
-	 * @param string $path
214
-	 * @return string
215
-	 */
216
-	public function file_get_contents($path) {
217
-		$encryptionModule = $this->getEncryptionModule($path);
218
-
219
-		if ($encryptionModule) {
220
-			$handle = $this->fopen($path, "r");
221
-			if (!$handle) {
222
-				return false;
223
-			}
224
-			$data = stream_get_contents($handle);
225
-			fclose($handle);
226
-			return $data;
227
-		}
228
-		return $this->storage->file_get_contents($path);
229
-	}
230
-
231
-	/**
232
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
233
-	 *
234
-	 * @param string $path
235
-	 * @param mixed $data
236
-	 * @return int|false
237
-	 */
238
-	public function file_put_contents($path, $data) {
239
-		// file put content will always be translated to a stream write
240
-		$handle = $this->fopen($path, 'w');
241
-		if (is_resource($handle)) {
242
-			$written = fwrite($handle, $data);
243
-			fclose($handle);
244
-			return $written;
245
-		}
246
-
247
-		return false;
248
-	}
249
-
250
-	/**
251
-	 * see https://www.php.net/manual/en/function.unlink.php
252
-	 *
253
-	 * @param string $path
254
-	 * @return bool
255
-	 */
256
-	public function unlink($path) {
257
-		$fullPath = $this->getFullPath($path);
258
-		if ($this->util->isExcluded($fullPath)) {
259
-			return $this->storage->unlink($path);
260
-		}
261
-
262
-		$encryptionModule = $this->getEncryptionModule($path);
263
-		if ($encryptionModule) {
264
-			$this->keyStorage->deleteAllFileKeys($fullPath);
265
-		}
266
-
267
-		return $this->storage->unlink($path);
268
-	}
269
-
270
-	/**
271
-	 * see https://www.php.net/manual/en/function.rename.php
272
-	 *
273
-	 * @param string $path1
274
-	 * @param string $path2
275
-	 * @return bool
276
-	 */
277
-	public function rename($path1, $path2) {
278
-		$result = $this->storage->rename($path1, $path2);
279
-
280
-		if ($result &&
281
-			// versions always use the keys from the original file, so we can skip
282
-			// this step for versions
283
-			$this->isVersion($path2) === false &&
284
-			$this->encryptionManager->isEnabled()) {
285
-			$source = $this->getFullPath($path1);
286
-			if (!$this->util->isExcluded($source)) {
287
-				$target = $this->getFullPath($path2);
288
-				if (isset($this->unencryptedSize[$source])) {
289
-					$this->unencryptedSize[$target] = $this->unencryptedSize[$source];
290
-				}
291
-				$this->keyStorage->renameKeys($source, $target);
292
-				$module = $this->getEncryptionModule($path2);
293
-				if ($module) {
294
-					$module->update($target, $this->uid, []);
295
-				}
296
-			}
297
-		}
298
-
299
-		return $result;
300
-	}
301
-
302
-	/**
303
-	 * see https://www.php.net/manual/en/function.rmdir.php
304
-	 *
305
-	 * @param string $path
306
-	 * @return bool
307
-	 */
308
-	public function rmdir($path) {
309
-		$result = $this->storage->rmdir($path);
310
-		$fullPath = $this->getFullPath($path);
311
-		if ($result &&
312
-			$this->util->isExcluded($fullPath) === false &&
313
-			$this->encryptionManager->isEnabled()
314
-		) {
315
-			$this->keyStorage->deleteAllFileKeys($fullPath);
316
-		}
317
-
318
-		return $result;
319
-	}
320
-
321
-	/**
322
-	 * check if a file can be read
323
-	 *
324
-	 * @param string $path
325
-	 * @return bool
326
-	 */
327
-	public function isReadable($path) {
328
-		$isReadable = true;
329
-
330
-		$metaData = $this->getMetaData($path);
331
-		if (
332
-			!$this->is_dir($path) &&
333
-			isset($metaData['encrypted']) &&
334
-			$metaData['encrypted'] === true
335
-		) {
336
-			$fullPath = $this->getFullPath($path);
337
-			$module = $this->getEncryptionModule($path);
338
-			$isReadable = $module->isReadable($fullPath, $this->uid);
339
-		}
340
-
341
-		return $this->storage->isReadable($path) && $isReadable;
342
-	}
343
-
344
-	/**
345
-	 * see https://www.php.net/manual/en/function.copy.php
346
-	 *
347
-	 * @param string $path1
348
-	 * @param string $path2
349
-	 * @return bool
350
-	 */
351
-	public function copy($path1, $path2) {
352
-		$source = $this->getFullPath($path1);
353
-
354
-		if ($this->util->isExcluded($source)) {
355
-			return $this->storage->copy($path1, $path2);
356
-		}
357
-
358
-		// need to stream copy file by file in case we copy between a encrypted
359
-		// and a unencrypted storage
360
-		$this->unlink($path2);
361
-		return $this->copyFromStorage($this, $path1, $path2);
362
-	}
363
-
364
-	/**
365
-	 * see https://www.php.net/manual/en/function.fopen.php
366
-	 *
367
-	 * @param string $path
368
-	 * @param string $mode
369
-	 * @return resource|bool
370
-	 * @throws GenericEncryptionException
371
-	 * @throws ModuleDoesNotExistsException
372
-	 */
373
-	public function fopen($path, $mode) {
374
-
375
-		// check if the file is stored in the array cache, this means that we
376
-		// copy a file over to the versions folder, in this case we don't want to
377
-		// decrypt it
378
-		if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
379
-			$this->arrayCache->remove('encryption_copy_version_' . $path);
380
-			return $this->storage->fopen($path, $mode);
381
-		}
382
-
383
-		$encryptionEnabled = $this->encryptionManager->isEnabled();
384
-		$shouldEncrypt = false;
385
-		$encryptionModule = null;
386
-		$header = $this->getHeader($path);
387
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
388
-		$fullPath = $this->getFullPath($path);
389
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
390
-
391
-		if ($this->util->isExcluded($fullPath) === false) {
392
-			$size = $unencryptedSize = 0;
393
-			$realFile = $this->util->stripPartialFileExtension($path);
394
-			$targetExists = $this->file_exists($realFile) || $this->file_exists($path);
395
-			$targetIsEncrypted = false;
396
-			if ($targetExists) {
397
-				// in case the file exists we require the explicit module as
398
-				// specified in the file header - otherwise we need to fail hard to
399
-				// prevent data loss on client side
400
-				if (!empty($encryptionModuleId)) {
401
-					$targetIsEncrypted = true;
402
-					$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
403
-				}
404
-
405
-				if ($this->file_exists($path)) {
406
-					$size = $this->storage->filesize($path);
407
-					$unencryptedSize = $this->filesize($path);
408
-				} else {
409
-					$size = $unencryptedSize = 0;
410
-				}
411
-			}
412
-
413
-			try {
414
-				if (
415
-					$mode === 'w'
416
-					|| $mode === 'w+'
417
-					|| $mode === 'wb'
418
-					|| $mode === 'wb+'
419
-				) {
420
-					// if we update a encrypted file with a un-encrypted one we change the db flag
421
-					if ($targetIsEncrypted && $encryptionEnabled === false) {
422
-						$cache = $this->storage->getCache();
423
-						if ($cache) {
424
-							$entry = $cache->get($path);
425
-							$cache->update($entry->getId(), ['encrypted' => 0]);
426
-						}
427
-					}
428
-					if ($encryptionEnabled) {
429
-						// if $encryptionModuleId is empty, the default module will be used
430
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
431
-						$shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
432
-						$signed = true;
433
-					}
434
-				} else {
435
-					$info = $this->getCache()->get($path);
436
-					// only get encryption module if we found one in the header
437
-					// or if file should be encrypted according to the file cache
438
-					if (!empty($encryptionModuleId)) {
439
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
440
-						$shouldEncrypt = true;
441
-					} elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
442
-						// we come from a old installation. No header and/or no module defined
443
-						// but the file is encrypted. In this case we need to use the
444
-						// OC_DEFAULT_MODULE to read the file
445
-						$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
446
-						$shouldEncrypt = true;
447
-						$targetIsEncrypted = true;
448
-					}
449
-				}
450
-			} catch (ModuleDoesNotExistsException $e) {
451
-				$this->logger->logException($e, [
452
-					'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
453
-					'level' => ILogger::WARN,
454
-					'app' => 'core',
455
-				]);
456
-			}
457
-
458
-			// encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
459
-			if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
460
-				if (!$targetExists || !$targetIsEncrypted) {
461
-					$shouldEncrypt = false;
462
-				}
463
-			}
464
-
465
-			if ($shouldEncrypt === true && $encryptionModule !== null) {
466
-				$headerSize = $this->getHeaderSize($path);
467
-				$source = $this->storage->fopen($path, $mode);
468
-				if (!is_resource($source)) {
469
-					return false;
470
-				}
471
-				$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
472
-					$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
473
-					$size, $unencryptedSize, $headerSize, $signed);
474
-				return $handle;
475
-			}
476
-		}
477
-
478
-		return $this->storage->fopen($path, $mode);
479
-	}
480
-
481
-
482
-	/**
483
-	 * perform some plausibility checks if the the unencrypted size is correct.
484
-	 * If not, we calculate the correct unencrypted size and return it
485
-	 *
486
-	 * @param string $path internal path relative to the storage root
487
-	 * @param int $unencryptedSize size of the unencrypted file
488
-	 *
489
-	 * @return int unencrypted size
490
-	 */
491
-	protected function verifyUnencryptedSize($path, $unencryptedSize) {
492
-		$size = $this->storage->filesize($path);
493
-		$result = $unencryptedSize;
494
-
495
-		if ($unencryptedSize < 0 ||
496
-			($size > 0 && $unencryptedSize === $size)
497
-		) {
498
-			// check if we already calculate the unencrypted size for the
499
-			// given path to avoid recursions
500
-			if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
501
-				$this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
502
-				try {
503
-					$result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
504
-				} catch (\Exception $e) {
505
-					$this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
506
-					$this->logger->logException($e);
507
-				}
508
-				unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
509
-			}
510
-		}
511
-
512
-		return $result;
513
-	}
514
-
515
-	/**
516
-	 * calculate the unencrypted size
517
-	 *
518
-	 * @param string $path internal path relative to the storage root
519
-	 * @param int $size size of the physical file
520
-	 * @param int $unencryptedSize size of the unencrypted file
521
-	 *
522
-	 * @return int calculated unencrypted size
523
-	 */
524
-	protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
525
-		$headerSize = $this->getHeaderSize($path);
526
-		$header = $this->getHeader($path);
527
-		$encryptionModule = $this->getEncryptionModule($path);
528
-
529
-		$stream = $this->storage->fopen($path, 'r');
530
-
531
-		// if we couldn't open the file we return the old unencrypted size
532
-		if (!is_resource($stream)) {
533
-			$this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
534
-			return $unencryptedSize;
535
-		}
536
-
537
-		$newUnencryptedSize = 0;
538
-		$size -= $headerSize;
539
-		$blockSize = $this->util->getBlockSize();
540
-
541
-		// if a header exists we skip it
542
-		if ($headerSize > 0) {
543
-			fread($stream, $headerSize);
544
-		}
545
-
546
-		// fast path, else the calculation for $lastChunkNr is bogus
547
-		if ($size === 0) {
548
-			return 0;
549
-		}
550
-
551
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
552
-		$unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
553
-
554
-		// calculate last chunk nr
555
-		// next highest is end of chunks, one subtracted is last one
556
-		// we have to read the last chunk, we can't just calculate it (because of padding etc)
557
-
558
-		$lastChunkNr = ceil($size / $blockSize) - 1;
559
-		// calculate last chunk position
560
-		$lastChunkPos = ($lastChunkNr * $blockSize);
561
-		// try to fseek to the last chunk, if it fails we have to read the whole file
562
-		if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
563
-			$newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
564
-		}
565
-
566
-		$lastChunkContentEncrypted = '';
567
-		$count = $blockSize;
568
-
569
-		while ($count > 0) {
570
-			$data = fread($stream, $blockSize);
571
-			$count = strlen($data);
572
-			$lastChunkContentEncrypted .= $data;
573
-			if (strlen($lastChunkContentEncrypted) > $blockSize) {
574
-				$newUnencryptedSize += $unencryptedBlockSize;
575
-				$lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
576
-			}
577
-		}
578
-
579
-		fclose($stream);
580
-
581
-		// we have to decrypt the last chunk to get it actual size
582
-		$encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
583
-		$decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
584
-		$decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
585
-
586
-		// calc the real file size with the size of the last chunk
587
-		$newUnencryptedSize += strlen($decryptedLastChunk);
588
-
589
-		$this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
590
-
591
-		// write to cache if applicable
592
-		$cache = $this->storage->getCache();
593
-		if ($cache) {
594
-			$entry = $cache->get($path);
595
-			$cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
596
-		}
597
-
598
-		return $newUnencryptedSize;
599
-	}
600
-
601
-	/**
602
-	 * @param Storage\IStorage $sourceStorage
603
-	 * @param string $sourceInternalPath
604
-	 * @param string $targetInternalPath
605
-	 * @param bool $preserveMtime
606
-	 * @return bool
607
-	 */
608
-	public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
609
-		if ($sourceStorage === $this) {
610
-			return $this->rename($sourceInternalPath, $targetInternalPath);
611
-		}
612
-
613
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
614
-		// - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
615
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
616
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
617
-		// - remove $this->copyBetweenStorage
618
-
619
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
620
-			return false;
621
-		}
622
-
623
-		$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
624
-		if ($result) {
625
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
626
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
627
-			} else {
628
-				$result &= $sourceStorage->unlink($sourceInternalPath);
629
-			}
630
-		}
631
-		return $result;
632
-	}
633
-
634
-
635
-	/**
636
-	 * @param Storage\IStorage $sourceStorage
637
-	 * @param string $sourceInternalPath
638
-	 * @param string $targetInternalPath
639
-	 * @param bool $preserveMtime
640
-	 * @param bool $isRename
641
-	 * @return bool
642
-	 */
643
-	public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
644
-
645
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
646
-		// - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
647
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
648
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
649
-		// - remove $this->copyBetweenStorage
650
-
651
-		return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
652
-	}
653
-
654
-	/**
655
-	 * Update the encrypted cache version in the database
656
-	 *
657
-	 * @param Storage\IStorage $sourceStorage
658
-	 * @param string $sourceInternalPath
659
-	 * @param string $targetInternalPath
660
-	 * @param bool $isRename
661
-	 * @param bool $keepEncryptionVersion
662
-	 */
663
-	private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
664
-		$isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
665
-		$cacheInformation = [
666
-			'encrypted' => $isEncrypted,
667
-		];
668
-		if ($isEncrypted) {
669
-			$encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
670
-
671
-			// In case of a move operation from an unencrypted to an encrypted
672
-			// storage the old encrypted version would stay with "0" while the
673
-			// correct value would be "1". Thus we manually set the value to "1"
674
-			// for those cases.
675
-			// See also https://github.com/owncloud/core/issues/23078
676
-			if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
677
-				$encryptedVersion = 1;
678
-			}
679
-
680
-			$cacheInformation['encryptedVersion'] = $encryptedVersion;
681
-		}
682
-
683
-		// in case of a rename we need to manipulate the source cache because
684
-		// this information will be kept for the new target
685
-		if ($isRename) {
686
-			$sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
687
-		} else {
688
-			$this->getCache()->put($targetInternalPath, $cacheInformation);
689
-		}
690
-	}
691
-
692
-	/**
693
-	 * copy file between two storages
694
-	 *
695
-	 * @param Storage\IStorage $sourceStorage
696
-	 * @param string $sourceInternalPath
697
-	 * @param string $targetInternalPath
698
-	 * @param bool $preserveMtime
699
-	 * @param bool $isRename
700
-	 * @return bool
701
-	 * @throws \Exception
702
-	 */
703
-	private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
704
-
705
-		// for versions we have nothing to do, because versions should always use the
706
-		// key from the original file. Just create a 1:1 copy and done
707
-		if ($this->isVersion($targetInternalPath) ||
708
-			$this->isVersion($sourceInternalPath)) {
709
-			// remember that we try to create a version so that we can detect it during
710
-			// fopen($sourceInternalPath) and by-pass the encryption in order to
711
-			// create a 1:1 copy of the file
712
-			$this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
713
-			$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
714
-			$this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
715
-			if ($result) {
716
-				$info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
717
-				// make sure that we update the unencrypted size for the version
718
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
719
-					$this->updateUnencryptedSize(
720
-						$this->getFullPath($targetInternalPath),
721
-						$info['size']
722
-					);
723
-				}
724
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
725
-			}
726
-			return $result;
727
-		}
728
-
729
-		// first copy the keys that we reuse the existing file key on the target location
730
-		// and don't create a new one which would break versions for example.
731
-		$mount = $this->mountManager->findByStorageId($sourceStorage->getId());
732
-		if (count($mount) === 1) {
733
-			$mountPoint = $mount[0]->getMountPoint();
734
-			$source = $mountPoint . '/' . $sourceInternalPath;
735
-			$target = $this->getFullPath($targetInternalPath);
736
-			$this->copyKeys($source, $target);
737
-		} else {
738
-			$this->logger->error('Could not find mount point, can\'t keep encryption keys');
739
-		}
740
-
741
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
742
-			$dh = $sourceStorage->opendir($sourceInternalPath);
743
-			$result = $this->mkdir($targetInternalPath);
744
-			if (is_resource($dh)) {
745
-				while ($result and ($file = readdir($dh)) !== false) {
746
-					if (!Filesystem::isIgnoredDir($file)) {
747
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
748
-					}
749
-				}
750
-			}
751
-		} else {
752
-			try {
753
-				$source = $sourceStorage->fopen($sourceInternalPath, 'r');
754
-				$target = $this->fopen($targetInternalPath, 'w');
755
-				[, $result] = \OC_Helper::streamCopy($source, $target);
756
-				fclose($source);
757
-				fclose($target);
758
-			} catch (\Exception $e) {
759
-				fclose($source);
760
-				fclose($target);
761
-				throw $e;
762
-			}
763
-			if ($result) {
764
-				if ($preserveMtime) {
765
-					$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
766
-				}
767
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
768
-			} else {
769
-				// delete partially written target file
770
-				$this->unlink($targetInternalPath);
771
-				// delete cache entry that was created by fopen
772
-				$this->getCache()->remove($targetInternalPath);
773
-			}
774
-		}
775
-		return (bool)$result;
776
-	}
777
-
778
-	/**
779
-	 * get the path to a local version of the file.
780
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
781
-	 *
782
-	 * @param string $path
783
-	 * @return string
784
-	 */
785
-	public function getLocalFile($path) {
786
-		if ($this->encryptionManager->isEnabled()) {
787
-			$cachedFile = $this->getCachedFile($path);
788
-			if (is_string($cachedFile)) {
789
-				return $cachedFile;
790
-			}
791
-		}
792
-		return $this->storage->getLocalFile($path);
793
-	}
794
-
795
-	/**
796
-	 * Returns the wrapped storage's value for isLocal()
797
-	 *
798
-	 * @return bool wrapped storage's isLocal() value
799
-	 */
800
-	public function isLocal() {
801
-		if ($this->encryptionManager->isEnabled()) {
802
-			return false;
803
-		}
804
-		return $this->storage->isLocal();
805
-	}
806
-
807
-	/**
808
-	 * see https://www.php.net/manual/en/function.stat.php
809
-	 * only the following keys are required in the result: size and mtime
810
-	 *
811
-	 * @param string $path
812
-	 * @return array
813
-	 */
814
-	public function stat($path) {
815
-		$stat = $this->storage->stat($path);
816
-		$fileSize = $this->filesize($path);
817
-		$stat['size'] = $fileSize;
818
-		$stat[7] = $fileSize;
819
-		$stat['hasHeader'] = $this->getHeaderSize($path) > 0;
820
-		return $stat;
821
-	}
822
-
823
-	/**
824
-	 * see https://www.php.net/manual/en/function.hash.php
825
-	 *
826
-	 * @param string $type
827
-	 * @param string $path
828
-	 * @param bool $raw
829
-	 * @return string
830
-	 */
831
-	public function hash($type, $path, $raw = false) {
832
-		$fh = $this->fopen($path, 'rb');
833
-		$ctx = hash_init($type);
834
-		hash_update_stream($ctx, $fh);
835
-		fclose($fh);
836
-		return hash_final($ctx, $raw);
837
-	}
838
-
839
-	/**
840
-	 * return full path, including mount point
841
-	 *
842
-	 * @param string $path relative to mount point
843
-	 * @return string full path including mount point
844
-	 */
845
-	protected function getFullPath($path) {
846
-		return Filesystem::normalizePath($this->mountPoint . '/' . $path);
847
-	}
848
-
849
-	/**
850
-	 * read first block of encrypted file, typically this will contain the
851
-	 * encryption header
852
-	 *
853
-	 * @param string $path
854
-	 * @return string
855
-	 */
856
-	protected function readFirstBlock($path) {
857
-		$firstBlock = '';
858
-		if ($this->storage->file_exists($path)) {
859
-			$handle = $this->storage->fopen($path, 'r');
860
-			$firstBlock = fread($handle, $this->util->getHeaderSize());
861
-			fclose($handle);
862
-		}
863
-		return $firstBlock;
864
-	}
865
-
866
-	/**
867
-	 * return header size of given file
868
-	 *
869
-	 * @param string $path
870
-	 * @return int
871
-	 */
872
-	protected function getHeaderSize($path) {
873
-		$headerSize = 0;
874
-		$realFile = $this->util->stripPartialFileExtension($path);
875
-		if ($this->storage->file_exists($realFile)) {
876
-			$path = $realFile;
877
-		}
878
-		$firstBlock = $this->readFirstBlock($path);
879
-
880
-		if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
881
-			$headerSize = $this->util->getHeaderSize();
882
-		}
883
-
884
-		return $headerSize;
885
-	}
886
-
887
-	/**
888
-	 * parse raw header to array
889
-	 *
890
-	 * @param string $rawHeader
891
-	 * @return array
892
-	 */
893
-	protected function parseRawHeader($rawHeader) {
894
-		$result = [];
895
-		if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
896
-			$header = $rawHeader;
897
-			$endAt = strpos($header, Util::HEADER_END);
898
-			if ($endAt !== false) {
899
-				$header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
900
-
901
-				// +1 to not start with an ':' which would result in empty element at the beginning
902
-				$exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
903
-
904
-				$element = array_shift($exploded);
905
-				while ($element !== Util::HEADER_END) {
906
-					$result[$element] = array_shift($exploded);
907
-					$element = array_shift($exploded);
908
-				}
909
-			}
910
-		}
911
-
912
-		return $result;
913
-	}
914
-
915
-	/**
916
-	 * read header from file
917
-	 *
918
-	 * @param string $path
919
-	 * @return array
920
-	 */
921
-	protected function getHeader($path) {
922
-		$realFile = $this->util->stripPartialFileExtension($path);
923
-		$exists = $this->storage->file_exists($realFile);
924
-		if ($exists) {
925
-			$path = $realFile;
926
-		}
927
-
928
-		$firstBlock = $this->readFirstBlock($path);
929
-		$result = $this->parseRawHeader($firstBlock);
930
-
931
-		// if the header doesn't contain a encryption module we check if it is a
932
-		// legacy file. If true, we add the default encryption module
933
-		if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
934
-			if (!empty($result)) {
935
-				$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
936
-			} elseif ($exists) {
937
-				// if the header was empty we have to check first if it is a encrypted file at all
938
-				// We would do query to filecache only if we know that entry in filecache exists
939
-				$info = $this->getCache()->get($path);
940
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
941
-					$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
942
-				}
943
-			}
944
-		}
945
-
946
-		return $result;
947
-	}
948
-
949
-	/**
950
-	 * read encryption module needed to read/write the file located at $path
951
-	 *
952
-	 * @param string $path
953
-	 * @return null|\OCP\Encryption\IEncryptionModule
954
-	 * @throws ModuleDoesNotExistsException
955
-	 * @throws \Exception
956
-	 */
957
-	protected function getEncryptionModule($path) {
958
-		$encryptionModule = null;
959
-		$header = $this->getHeader($path);
960
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
961
-		if (!empty($encryptionModuleId)) {
962
-			try {
963
-				$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
964
-			} catch (ModuleDoesNotExistsException $e) {
965
-				$this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
966
-				throw $e;
967
-			}
968
-		}
969
-
970
-		return $encryptionModule;
971
-	}
972
-
973
-	/**
974
-	 * @param string $path
975
-	 * @param int $unencryptedSize
976
-	 */
977
-	public function updateUnencryptedSize($path, $unencryptedSize) {
978
-		$this->unencryptedSize[$path] = $unencryptedSize;
979
-	}
980
-
981
-	/**
982
-	 * copy keys to new location
983
-	 *
984
-	 * @param string $source path relative to data/
985
-	 * @param string $target path relative to data/
986
-	 * @return bool
987
-	 */
988
-	protected function copyKeys($source, $target) {
989
-		if (!$this->util->isExcluded($source)) {
990
-			return $this->keyStorage->copyKeys($source, $target);
991
-		}
992
-
993
-		return false;
994
-	}
995
-
996
-	/**
997
-	 * check if path points to a files version
998
-	 *
999
-	 * @param $path
1000
-	 * @return bool
1001
-	 */
1002
-	protected function isVersion($path) {
1003
-		$normalized = Filesystem::normalizePath($path);
1004
-		return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1005
-	}
1006
-
1007
-	/**
1008
-	 * check if the given storage should be encrypted or not
1009
-	 *
1010
-	 * @param $path
1011
-	 * @return bool
1012
-	 */
1013
-	protected function shouldEncrypt($path) {
1014
-		$fullPath = $this->getFullPath($path);
1015
-		$mountPointConfig = $this->mount->getOption('encrypt', true);
1016
-		if ($mountPointConfig === false) {
1017
-			return false;
1018
-		}
1019
-
1020
-		try {
1021
-			$encryptionModule = $this->getEncryptionModule($fullPath);
1022
-		} catch (ModuleDoesNotExistsException $e) {
1023
-			return false;
1024
-		}
1025
-
1026
-		if ($encryptionModule === null) {
1027
-			$encryptionModule = $this->encryptionManager->getEncryptionModule();
1028
-		}
1029
-
1030
-		return $encryptionModule->shouldEncrypt($fullPath);
1031
-	}
1032
-
1033
-	public function writeStream(string $path, $stream, int $size = null): int {
1034
-		// always fall back to fopen
1035
-		$target = $this->fopen($path, 'w');
1036
-		[$count, $result] = \OC_Helper::streamCopy($stream, $target);
1037
-		fclose($target);
1038
-		return $count;
1039
-	}
56
+    use LocalTempFileTrait;
57
+
58
+    /** @var string */
59
+    private $mountPoint;
60
+
61
+    /** @var \OC\Encryption\Util */
62
+    private $util;
63
+
64
+    /** @var \OCP\Encryption\IManager */
65
+    private $encryptionManager;
66
+
67
+    /** @var \OCP\ILogger */
68
+    private $logger;
69
+
70
+    /** @var string */
71
+    private $uid;
72
+
73
+    /** @var array */
74
+    protected $unencryptedSize;
75
+
76
+    /** @var \OCP\Encryption\IFile */
77
+    private $fileHelper;
78
+
79
+    /** @var IMountPoint */
80
+    private $mount;
81
+
82
+    /** @var IStorage */
83
+    private $keyStorage;
84
+
85
+    /** @var Update */
86
+    private $update;
87
+
88
+    /** @var Manager */
89
+    private $mountManager;
90
+
91
+    /** @var array remember for which path we execute the repair step to avoid recursions */
92
+    private $fixUnencryptedSizeOf = [];
93
+
94
+    /** @var  ArrayCache */
95
+    private $arrayCache;
96
+
97
+    /**
98
+     * @param array $parameters
99
+     * @param IManager $encryptionManager
100
+     * @param Util $util
101
+     * @param ILogger $logger
102
+     * @param IFile $fileHelper
103
+     * @param string $uid
104
+     * @param IStorage $keyStorage
105
+     * @param Update $update
106
+     * @param Manager $mountManager
107
+     * @param ArrayCache $arrayCache
108
+     */
109
+    public function __construct(
110
+        $parameters,
111
+        IManager $encryptionManager = null,
112
+        Util $util = null,
113
+        ILogger $logger = null,
114
+        IFile $fileHelper = null,
115
+        $uid = null,
116
+        IStorage $keyStorage = null,
117
+        Update $update = null,
118
+        Manager $mountManager = null,
119
+        ArrayCache $arrayCache = null
120
+    ) {
121
+        $this->mountPoint = $parameters['mountPoint'];
122
+        $this->mount = $parameters['mount'];
123
+        $this->encryptionManager = $encryptionManager;
124
+        $this->util = $util;
125
+        $this->logger = $logger;
126
+        $this->uid = $uid;
127
+        $this->fileHelper = $fileHelper;
128
+        $this->keyStorage = $keyStorage;
129
+        $this->unencryptedSize = [];
130
+        $this->update = $update;
131
+        $this->mountManager = $mountManager;
132
+        $this->arrayCache = $arrayCache;
133
+        parent::__construct($parameters);
134
+    }
135
+
136
+    /**
137
+     * see https://www.php.net/manual/en/function.filesize.php
138
+     * The result for filesize when called on a folder is required to be 0
139
+     *
140
+     * @param string $path
141
+     * @return int
142
+     */
143
+    public function filesize($path) {
144
+        $fullPath = $this->getFullPath($path);
145
+
146
+        /** @var CacheEntry $info */
147
+        $info = $this->getCache()->get($path);
148
+        if (isset($this->unencryptedSize[$fullPath])) {
149
+            $size = $this->unencryptedSize[$fullPath];
150
+            // update file cache
151
+            if ($info instanceof ICacheEntry) {
152
+                $info = $info->getData();
153
+                $info['encrypted'] = $info['encryptedVersion'];
154
+            } else {
155
+                if (!is_array($info)) {
156
+                    $info = [];
157
+                }
158
+                $info['encrypted'] = true;
159
+            }
160
+
161
+            $info['size'] = $size;
162
+            $this->getCache()->put($path, $info);
163
+
164
+            return $size;
165
+        }
166
+
167
+        if (isset($info['fileid']) && $info['encrypted']) {
168
+            return $this->verifyUnencryptedSize($path, $info['size']);
169
+        }
170
+
171
+        return $this->storage->filesize($path);
172
+    }
173
+
174
+    private function modifyMetaData(string $path, array $data): array {
175
+        $fullPath = $this->getFullPath($path);
176
+        $info = $this->getCache()->get($path);
177
+
178
+        if (isset($this->unencryptedSize[$fullPath])) {
179
+            $data['encrypted'] = true;
180
+            $data['size'] = $this->unencryptedSize[$fullPath];
181
+        } else {
182
+            if (isset($info['fileid']) && $info['encrypted']) {
183
+                $data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
184
+                $data['encrypted'] = true;
185
+            }
186
+        }
187
+
188
+        if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
189
+            $data['encryptedVersion'] = $info['encryptedVersion'];
190
+        }
191
+
192
+        return $data;
193
+    }
194
+
195
+    public function getMetaData($path) {
196
+        $data = $this->storage->getMetaData($path);
197
+        if (is_null($data)) {
198
+            return null;
199
+        }
200
+        return $this->modifyMetaData($path, $data);
201
+    }
202
+
203
+    public function getDirectoryContent($directory): \Traversable {
204
+        $parent = rtrim($directory, '/');
205
+        foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
206
+            yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
207
+        }
208
+    }
209
+
210
+    /**
211
+     * see https://www.php.net/manual/en/function.file_get_contents.php
212
+     *
213
+     * @param string $path
214
+     * @return string
215
+     */
216
+    public function file_get_contents($path) {
217
+        $encryptionModule = $this->getEncryptionModule($path);
218
+
219
+        if ($encryptionModule) {
220
+            $handle = $this->fopen($path, "r");
221
+            if (!$handle) {
222
+                return false;
223
+            }
224
+            $data = stream_get_contents($handle);
225
+            fclose($handle);
226
+            return $data;
227
+        }
228
+        return $this->storage->file_get_contents($path);
229
+    }
230
+
231
+    /**
232
+     * see https://www.php.net/manual/en/function.file_put_contents.php
233
+     *
234
+     * @param string $path
235
+     * @param mixed $data
236
+     * @return int|false
237
+     */
238
+    public function file_put_contents($path, $data) {
239
+        // file put content will always be translated to a stream write
240
+        $handle = $this->fopen($path, 'w');
241
+        if (is_resource($handle)) {
242
+            $written = fwrite($handle, $data);
243
+            fclose($handle);
244
+            return $written;
245
+        }
246
+
247
+        return false;
248
+    }
249
+
250
+    /**
251
+     * see https://www.php.net/manual/en/function.unlink.php
252
+     *
253
+     * @param string $path
254
+     * @return bool
255
+     */
256
+    public function unlink($path) {
257
+        $fullPath = $this->getFullPath($path);
258
+        if ($this->util->isExcluded($fullPath)) {
259
+            return $this->storage->unlink($path);
260
+        }
261
+
262
+        $encryptionModule = $this->getEncryptionModule($path);
263
+        if ($encryptionModule) {
264
+            $this->keyStorage->deleteAllFileKeys($fullPath);
265
+        }
266
+
267
+        return $this->storage->unlink($path);
268
+    }
269
+
270
+    /**
271
+     * see https://www.php.net/manual/en/function.rename.php
272
+     *
273
+     * @param string $path1
274
+     * @param string $path2
275
+     * @return bool
276
+     */
277
+    public function rename($path1, $path2) {
278
+        $result = $this->storage->rename($path1, $path2);
279
+
280
+        if ($result &&
281
+            // versions always use the keys from the original file, so we can skip
282
+            // this step for versions
283
+            $this->isVersion($path2) === false &&
284
+            $this->encryptionManager->isEnabled()) {
285
+            $source = $this->getFullPath($path1);
286
+            if (!$this->util->isExcluded($source)) {
287
+                $target = $this->getFullPath($path2);
288
+                if (isset($this->unencryptedSize[$source])) {
289
+                    $this->unencryptedSize[$target] = $this->unencryptedSize[$source];
290
+                }
291
+                $this->keyStorage->renameKeys($source, $target);
292
+                $module = $this->getEncryptionModule($path2);
293
+                if ($module) {
294
+                    $module->update($target, $this->uid, []);
295
+                }
296
+            }
297
+        }
298
+
299
+        return $result;
300
+    }
301
+
302
+    /**
303
+     * see https://www.php.net/manual/en/function.rmdir.php
304
+     *
305
+     * @param string $path
306
+     * @return bool
307
+     */
308
+    public function rmdir($path) {
309
+        $result = $this->storage->rmdir($path);
310
+        $fullPath = $this->getFullPath($path);
311
+        if ($result &&
312
+            $this->util->isExcluded($fullPath) === false &&
313
+            $this->encryptionManager->isEnabled()
314
+        ) {
315
+            $this->keyStorage->deleteAllFileKeys($fullPath);
316
+        }
317
+
318
+        return $result;
319
+    }
320
+
321
+    /**
322
+     * check if a file can be read
323
+     *
324
+     * @param string $path
325
+     * @return bool
326
+     */
327
+    public function isReadable($path) {
328
+        $isReadable = true;
329
+
330
+        $metaData = $this->getMetaData($path);
331
+        if (
332
+            !$this->is_dir($path) &&
333
+            isset($metaData['encrypted']) &&
334
+            $metaData['encrypted'] === true
335
+        ) {
336
+            $fullPath = $this->getFullPath($path);
337
+            $module = $this->getEncryptionModule($path);
338
+            $isReadable = $module->isReadable($fullPath, $this->uid);
339
+        }
340
+
341
+        return $this->storage->isReadable($path) && $isReadable;
342
+    }
343
+
344
+    /**
345
+     * see https://www.php.net/manual/en/function.copy.php
346
+     *
347
+     * @param string $path1
348
+     * @param string $path2
349
+     * @return bool
350
+     */
351
+    public function copy($path1, $path2) {
352
+        $source = $this->getFullPath($path1);
353
+
354
+        if ($this->util->isExcluded($source)) {
355
+            return $this->storage->copy($path1, $path2);
356
+        }
357
+
358
+        // need to stream copy file by file in case we copy between a encrypted
359
+        // and a unencrypted storage
360
+        $this->unlink($path2);
361
+        return $this->copyFromStorage($this, $path1, $path2);
362
+    }
363
+
364
+    /**
365
+     * see https://www.php.net/manual/en/function.fopen.php
366
+     *
367
+     * @param string $path
368
+     * @param string $mode
369
+     * @return resource|bool
370
+     * @throws GenericEncryptionException
371
+     * @throws ModuleDoesNotExistsException
372
+     */
373
+    public function fopen($path, $mode) {
374
+
375
+        // check if the file is stored in the array cache, this means that we
376
+        // copy a file over to the versions folder, in this case we don't want to
377
+        // decrypt it
378
+        if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
379
+            $this->arrayCache->remove('encryption_copy_version_' . $path);
380
+            return $this->storage->fopen($path, $mode);
381
+        }
382
+
383
+        $encryptionEnabled = $this->encryptionManager->isEnabled();
384
+        $shouldEncrypt = false;
385
+        $encryptionModule = null;
386
+        $header = $this->getHeader($path);
387
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
388
+        $fullPath = $this->getFullPath($path);
389
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
390
+
391
+        if ($this->util->isExcluded($fullPath) === false) {
392
+            $size = $unencryptedSize = 0;
393
+            $realFile = $this->util->stripPartialFileExtension($path);
394
+            $targetExists = $this->file_exists($realFile) || $this->file_exists($path);
395
+            $targetIsEncrypted = false;
396
+            if ($targetExists) {
397
+                // in case the file exists we require the explicit module as
398
+                // specified in the file header - otherwise we need to fail hard to
399
+                // prevent data loss on client side
400
+                if (!empty($encryptionModuleId)) {
401
+                    $targetIsEncrypted = true;
402
+                    $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
403
+                }
404
+
405
+                if ($this->file_exists($path)) {
406
+                    $size = $this->storage->filesize($path);
407
+                    $unencryptedSize = $this->filesize($path);
408
+                } else {
409
+                    $size = $unencryptedSize = 0;
410
+                }
411
+            }
412
+
413
+            try {
414
+                if (
415
+                    $mode === 'w'
416
+                    || $mode === 'w+'
417
+                    || $mode === 'wb'
418
+                    || $mode === 'wb+'
419
+                ) {
420
+                    // if we update a encrypted file with a un-encrypted one we change the db flag
421
+                    if ($targetIsEncrypted && $encryptionEnabled === false) {
422
+                        $cache = $this->storage->getCache();
423
+                        if ($cache) {
424
+                            $entry = $cache->get($path);
425
+                            $cache->update($entry->getId(), ['encrypted' => 0]);
426
+                        }
427
+                    }
428
+                    if ($encryptionEnabled) {
429
+                        // if $encryptionModuleId is empty, the default module will be used
430
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
431
+                        $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
432
+                        $signed = true;
433
+                    }
434
+                } else {
435
+                    $info = $this->getCache()->get($path);
436
+                    // only get encryption module if we found one in the header
437
+                    // or if file should be encrypted according to the file cache
438
+                    if (!empty($encryptionModuleId)) {
439
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
440
+                        $shouldEncrypt = true;
441
+                    } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
442
+                        // we come from a old installation. No header and/or no module defined
443
+                        // but the file is encrypted. In this case we need to use the
444
+                        // OC_DEFAULT_MODULE to read the file
445
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
446
+                        $shouldEncrypt = true;
447
+                        $targetIsEncrypted = true;
448
+                    }
449
+                }
450
+            } catch (ModuleDoesNotExistsException $e) {
451
+                $this->logger->logException($e, [
452
+                    'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
453
+                    'level' => ILogger::WARN,
454
+                    'app' => 'core',
455
+                ]);
456
+            }
457
+
458
+            // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
459
+            if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
460
+                if (!$targetExists || !$targetIsEncrypted) {
461
+                    $shouldEncrypt = false;
462
+                }
463
+            }
464
+
465
+            if ($shouldEncrypt === true && $encryptionModule !== null) {
466
+                $headerSize = $this->getHeaderSize($path);
467
+                $source = $this->storage->fopen($path, $mode);
468
+                if (!is_resource($source)) {
469
+                    return false;
470
+                }
471
+                $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
472
+                    $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
473
+                    $size, $unencryptedSize, $headerSize, $signed);
474
+                return $handle;
475
+            }
476
+        }
477
+
478
+        return $this->storage->fopen($path, $mode);
479
+    }
480
+
481
+
482
+    /**
483
+     * perform some plausibility checks if the the unencrypted size is correct.
484
+     * If not, we calculate the correct unencrypted size and return it
485
+     *
486
+     * @param string $path internal path relative to the storage root
487
+     * @param int $unencryptedSize size of the unencrypted file
488
+     *
489
+     * @return int unencrypted size
490
+     */
491
+    protected function verifyUnencryptedSize($path, $unencryptedSize) {
492
+        $size = $this->storage->filesize($path);
493
+        $result = $unencryptedSize;
494
+
495
+        if ($unencryptedSize < 0 ||
496
+            ($size > 0 && $unencryptedSize === $size)
497
+        ) {
498
+            // check if we already calculate the unencrypted size for the
499
+            // given path to avoid recursions
500
+            if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
501
+                $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
502
+                try {
503
+                    $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
504
+                } catch (\Exception $e) {
505
+                    $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
506
+                    $this->logger->logException($e);
507
+                }
508
+                unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
509
+            }
510
+        }
511
+
512
+        return $result;
513
+    }
514
+
515
+    /**
516
+     * calculate the unencrypted size
517
+     *
518
+     * @param string $path internal path relative to the storage root
519
+     * @param int $size size of the physical file
520
+     * @param int $unencryptedSize size of the unencrypted file
521
+     *
522
+     * @return int calculated unencrypted size
523
+     */
524
+    protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
525
+        $headerSize = $this->getHeaderSize($path);
526
+        $header = $this->getHeader($path);
527
+        $encryptionModule = $this->getEncryptionModule($path);
528
+
529
+        $stream = $this->storage->fopen($path, 'r');
530
+
531
+        // if we couldn't open the file we return the old unencrypted size
532
+        if (!is_resource($stream)) {
533
+            $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
534
+            return $unencryptedSize;
535
+        }
536
+
537
+        $newUnencryptedSize = 0;
538
+        $size -= $headerSize;
539
+        $blockSize = $this->util->getBlockSize();
540
+
541
+        // if a header exists we skip it
542
+        if ($headerSize > 0) {
543
+            fread($stream, $headerSize);
544
+        }
545
+
546
+        // fast path, else the calculation for $lastChunkNr is bogus
547
+        if ($size === 0) {
548
+            return 0;
549
+        }
550
+
551
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
552
+        $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
553
+
554
+        // calculate last chunk nr
555
+        // next highest is end of chunks, one subtracted is last one
556
+        // we have to read the last chunk, we can't just calculate it (because of padding etc)
557
+
558
+        $lastChunkNr = ceil($size / $blockSize) - 1;
559
+        // calculate last chunk position
560
+        $lastChunkPos = ($lastChunkNr * $blockSize);
561
+        // try to fseek to the last chunk, if it fails we have to read the whole file
562
+        if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
563
+            $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
564
+        }
565
+
566
+        $lastChunkContentEncrypted = '';
567
+        $count = $blockSize;
568
+
569
+        while ($count > 0) {
570
+            $data = fread($stream, $blockSize);
571
+            $count = strlen($data);
572
+            $lastChunkContentEncrypted .= $data;
573
+            if (strlen($lastChunkContentEncrypted) > $blockSize) {
574
+                $newUnencryptedSize += $unencryptedBlockSize;
575
+                $lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
576
+            }
577
+        }
578
+
579
+        fclose($stream);
580
+
581
+        // we have to decrypt the last chunk to get it actual size
582
+        $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
583
+        $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
584
+        $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
585
+
586
+        // calc the real file size with the size of the last chunk
587
+        $newUnencryptedSize += strlen($decryptedLastChunk);
588
+
589
+        $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
590
+
591
+        // write to cache if applicable
592
+        $cache = $this->storage->getCache();
593
+        if ($cache) {
594
+            $entry = $cache->get($path);
595
+            $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
596
+        }
597
+
598
+        return $newUnencryptedSize;
599
+    }
600
+
601
+    /**
602
+     * @param Storage\IStorage $sourceStorage
603
+     * @param string $sourceInternalPath
604
+     * @param string $targetInternalPath
605
+     * @param bool $preserveMtime
606
+     * @return bool
607
+     */
608
+    public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
609
+        if ($sourceStorage === $this) {
610
+            return $this->rename($sourceInternalPath, $targetInternalPath);
611
+        }
612
+
613
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
614
+        // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
615
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
616
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
617
+        // - remove $this->copyBetweenStorage
618
+
619
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
620
+            return false;
621
+        }
622
+
623
+        $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
624
+        if ($result) {
625
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
626
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
627
+            } else {
628
+                $result &= $sourceStorage->unlink($sourceInternalPath);
629
+            }
630
+        }
631
+        return $result;
632
+    }
633
+
634
+
635
+    /**
636
+     * @param Storage\IStorage $sourceStorage
637
+     * @param string $sourceInternalPath
638
+     * @param string $targetInternalPath
639
+     * @param bool $preserveMtime
640
+     * @param bool $isRename
641
+     * @return bool
642
+     */
643
+    public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
644
+
645
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
646
+        // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
647
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
648
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
649
+        // - remove $this->copyBetweenStorage
650
+
651
+        return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
652
+    }
653
+
654
+    /**
655
+     * Update the encrypted cache version in the database
656
+     *
657
+     * @param Storage\IStorage $sourceStorage
658
+     * @param string $sourceInternalPath
659
+     * @param string $targetInternalPath
660
+     * @param bool $isRename
661
+     * @param bool $keepEncryptionVersion
662
+     */
663
+    private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
664
+        $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
665
+        $cacheInformation = [
666
+            'encrypted' => $isEncrypted,
667
+        ];
668
+        if ($isEncrypted) {
669
+            $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
670
+
671
+            // In case of a move operation from an unencrypted to an encrypted
672
+            // storage the old encrypted version would stay with "0" while the
673
+            // correct value would be "1". Thus we manually set the value to "1"
674
+            // for those cases.
675
+            // See also https://github.com/owncloud/core/issues/23078
676
+            if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
677
+                $encryptedVersion = 1;
678
+            }
679
+
680
+            $cacheInformation['encryptedVersion'] = $encryptedVersion;
681
+        }
682
+
683
+        // in case of a rename we need to manipulate the source cache because
684
+        // this information will be kept for the new target
685
+        if ($isRename) {
686
+            $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
687
+        } else {
688
+            $this->getCache()->put($targetInternalPath, $cacheInformation);
689
+        }
690
+    }
691
+
692
+    /**
693
+     * copy file between two storages
694
+     *
695
+     * @param Storage\IStorage $sourceStorage
696
+     * @param string $sourceInternalPath
697
+     * @param string $targetInternalPath
698
+     * @param bool $preserveMtime
699
+     * @param bool $isRename
700
+     * @return bool
701
+     * @throws \Exception
702
+     */
703
+    private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
704
+
705
+        // for versions we have nothing to do, because versions should always use the
706
+        // key from the original file. Just create a 1:1 copy and done
707
+        if ($this->isVersion($targetInternalPath) ||
708
+            $this->isVersion($sourceInternalPath)) {
709
+            // remember that we try to create a version so that we can detect it during
710
+            // fopen($sourceInternalPath) and by-pass the encryption in order to
711
+            // create a 1:1 copy of the file
712
+            $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
713
+            $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
714
+            $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
715
+            if ($result) {
716
+                $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
717
+                // make sure that we update the unencrypted size for the version
718
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
719
+                    $this->updateUnencryptedSize(
720
+                        $this->getFullPath($targetInternalPath),
721
+                        $info['size']
722
+                    );
723
+                }
724
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
725
+            }
726
+            return $result;
727
+        }
728
+
729
+        // first copy the keys that we reuse the existing file key on the target location
730
+        // and don't create a new one which would break versions for example.
731
+        $mount = $this->mountManager->findByStorageId($sourceStorage->getId());
732
+        if (count($mount) === 1) {
733
+            $mountPoint = $mount[0]->getMountPoint();
734
+            $source = $mountPoint . '/' . $sourceInternalPath;
735
+            $target = $this->getFullPath($targetInternalPath);
736
+            $this->copyKeys($source, $target);
737
+        } else {
738
+            $this->logger->error('Could not find mount point, can\'t keep encryption keys');
739
+        }
740
+
741
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
742
+            $dh = $sourceStorage->opendir($sourceInternalPath);
743
+            $result = $this->mkdir($targetInternalPath);
744
+            if (is_resource($dh)) {
745
+                while ($result and ($file = readdir($dh)) !== false) {
746
+                    if (!Filesystem::isIgnoredDir($file)) {
747
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
748
+                    }
749
+                }
750
+            }
751
+        } else {
752
+            try {
753
+                $source = $sourceStorage->fopen($sourceInternalPath, 'r');
754
+                $target = $this->fopen($targetInternalPath, 'w');
755
+                [, $result] = \OC_Helper::streamCopy($source, $target);
756
+                fclose($source);
757
+                fclose($target);
758
+            } catch (\Exception $e) {
759
+                fclose($source);
760
+                fclose($target);
761
+                throw $e;
762
+            }
763
+            if ($result) {
764
+                if ($preserveMtime) {
765
+                    $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
766
+                }
767
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
768
+            } else {
769
+                // delete partially written target file
770
+                $this->unlink($targetInternalPath);
771
+                // delete cache entry that was created by fopen
772
+                $this->getCache()->remove($targetInternalPath);
773
+            }
774
+        }
775
+        return (bool)$result;
776
+    }
777
+
778
+    /**
779
+     * get the path to a local version of the file.
780
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
781
+     *
782
+     * @param string $path
783
+     * @return string
784
+     */
785
+    public function getLocalFile($path) {
786
+        if ($this->encryptionManager->isEnabled()) {
787
+            $cachedFile = $this->getCachedFile($path);
788
+            if (is_string($cachedFile)) {
789
+                return $cachedFile;
790
+            }
791
+        }
792
+        return $this->storage->getLocalFile($path);
793
+    }
794
+
795
+    /**
796
+     * Returns the wrapped storage's value for isLocal()
797
+     *
798
+     * @return bool wrapped storage's isLocal() value
799
+     */
800
+    public function isLocal() {
801
+        if ($this->encryptionManager->isEnabled()) {
802
+            return false;
803
+        }
804
+        return $this->storage->isLocal();
805
+    }
806
+
807
+    /**
808
+     * see https://www.php.net/manual/en/function.stat.php
809
+     * only the following keys are required in the result: size and mtime
810
+     *
811
+     * @param string $path
812
+     * @return array
813
+     */
814
+    public function stat($path) {
815
+        $stat = $this->storage->stat($path);
816
+        $fileSize = $this->filesize($path);
817
+        $stat['size'] = $fileSize;
818
+        $stat[7] = $fileSize;
819
+        $stat['hasHeader'] = $this->getHeaderSize($path) > 0;
820
+        return $stat;
821
+    }
822
+
823
+    /**
824
+     * see https://www.php.net/manual/en/function.hash.php
825
+     *
826
+     * @param string $type
827
+     * @param string $path
828
+     * @param bool $raw
829
+     * @return string
830
+     */
831
+    public function hash($type, $path, $raw = false) {
832
+        $fh = $this->fopen($path, 'rb');
833
+        $ctx = hash_init($type);
834
+        hash_update_stream($ctx, $fh);
835
+        fclose($fh);
836
+        return hash_final($ctx, $raw);
837
+    }
838
+
839
+    /**
840
+     * return full path, including mount point
841
+     *
842
+     * @param string $path relative to mount point
843
+     * @return string full path including mount point
844
+     */
845
+    protected function getFullPath($path) {
846
+        return Filesystem::normalizePath($this->mountPoint . '/' . $path);
847
+    }
848
+
849
+    /**
850
+     * read first block of encrypted file, typically this will contain the
851
+     * encryption header
852
+     *
853
+     * @param string $path
854
+     * @return string
855
+     */
856
+    protected function readFirstBlock($path) {
857
+        $firstBlock = '';
858
+        if ($this->storage->file_exists($path)) {
859
+            $handle = $this->storage->fopen($path, 'r');
860
+            $firstBlock = fread($handle, $this->util->getHeaderSize());
861
+            fclose($handle);
862
+        }
863
+        return $firstBlock;
864
+    }
865
+
866
+    /**
867
+     * return header size of given file
868
+     *
869
+     * @param string $path
870
+     * @return int
871
+     */
872
+    protected function getHeaderSize($path) {
873
+        $headerSize = 0;
874
+        $realFile = $this->util->stripPartialFileExtension($path);
875
+        if ($this->storage->file_exists($realFile)) {
876
+            $path = $realFile;
877
+        }
878
+        $firstBlock = $this->readFirstBlock($path);
879
+
880
+        if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
881
+            $headerSize = $this->util->getHeaderSize();
882
+        }
883
+
884
+        return $headerSize;
885
+    }
886
+
887
+    /**
888
+     * parse raw header to array
889
+     *
890
+     * @param string $rawHeader
891
+     * @return array
892
+     */
893
+    protected function parseRawHeader($rawHeader) {
894
+        $result = [];
895
+        if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
896
+            $header = $rawHeader;
897
+            $endAt = strpos($header, Util::HEADER_END);
898
+            if ($endAt !== false) {
899
+                $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
900
+
901
+                // +1 to not start with an ':' which would result in empty element at the beginning
902
+                $exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
903
+
904
+                $element = array_shift($exploded);
905
+                while ($element !== Util::HEADER_END) {
906
+                    $result[$element] = array_shift($exploded);
907
+                    $element = array_shift($exploded);
908
+                }
909
+            }
910
+        }
911
+
912
+        return $result;
913
+    }
914
+
915
+    /**
916
+     * read header from file
917
+     *
918
+     * @param string $path
919
+     * @return array
920
+     */
921
+    protected function getHeader($path) {
922
+        $realFile = $this->util->stripPartialFileExtension($path);
923
+        $exists = $this->storage->file_exists($realFile);
924
+        if ($exists) {
925
+            $path = $realFile;
926
+        }
927
+
928
+        $firstBlock = $this->readFirstBlock($path);
929
+        $result = $this->parseRawHeader($firstBlock);
930
+
931
+        // if the header doesn't contain a encryption module we check if it is a
932
+        // legacy file. If true, we add the default encryption module
933
+        if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
934
+            if (!empty($result)) {
935
+                $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
936
+            } elseif ($exists) {
937
+                // if the header was empty we have to check first if it is a encrypted file at all
938
+                // We would do query to filecache only if we know that entry in filecache exists
939
+                $info = $this->getCache()->get($path);
940
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
941
+                    $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
942
+                }
943
+            }
944
+        }
945
+
946
+        return $result;
947
+    }
948
+
949
+    /**
950
+     * read encryption module needed to read/write the file located at $path
951
+     *
952
+     * @param string $path
953
+     * @return null|\OCP\Encryption\IEncryptionModule
954
+     * @throws ModuleDoesNotExistsException
955
+     * @throws \Exception
956
+     */
957
+    protected function getEncryptionModule($path) {
958
+        $encryptionModule = null;
959
+        $header = $this->getHeader($path);
960
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
961
+        if (!empty($encryptionModuleId)) {
962
+            try {
963
+                $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
964
+            } catch (ModuleDoesNotExistsException $e) {
965
+                $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
966
+                throw $e;
967
+            }
968
+        }
969
+
970
+        return $encryptionModule;
971
+    }
972
+
973
+    /**
974
+     * @param string $path
975
+     * @param int $unencryptedSize
976
+     */
977
+    public function updateUnencryptedSize($path, $unencryptedSize) {
978
+        $this->unencryptedSize[$path] = $unencryptedSize;
979
+    }
980
+
981
+    /**
982
+     * copy keys to new location
983
+     *
984
+     * @param string $source path relative to data/
985
+     * @param string $target path relative to data/
986
+     * @return bool
987
+     */
988
+    protected function copyKeys($source, $target) {
989
+        if (!$this->util->isExcluded($source)) {
990
+            return $this->keyStorage->copyKeys($source, $target);
991
+        }
992
+
993
+        return false;
994
+    }
995
+
996
+    /**
997
+     * check if path points to a files version
998
+     *
999
+     * @param $path
1000
+     * @return bool
1001
+     */
1002
+    protected function isVersion($path) {
1003
+        $normalized = Filesystem::normalizePath($path);
1004
+        return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1005
+    }
1006
+
1007
+    /**
1008
+     * check if the given storage should be encrypted or not
1009
+     *
1010
+     * @param $path
1011
+     * @return bool
1012
+     */
1013
+    protected function shouldEncrypt($path) {
1014
+        $fullPath = $this->getFullPath($path);
1015
+        $mountPointConfig = $this->mount->getOption('encrypt', true);
1016
+        if ($mountPointConfig === false) {
1017
+            return false;
1018
+        }
1019
+
1020
+        try {
1021
+            $encryptionModule = $this->getEncryptionModule($fullPath);
1022
+        } catch (ModuleDoesNotExistsException $e) {
1023
+            return false;
1024
+        }
1025
+
1026
+        if ($encryptionModule === null) {
1027
+            $encryptionModule = $this->encryptionManager->getEncryptionModule();
1028
+        }
1029
+
1030
+        return $encryptionModule->shouldEncrypt($fullPath);
1031
+    }
1032
+
1033
+    public function writeStream(string $path, $stream, int $size = null): int {
1034
+        // always fall back to fopen
1035
+        $target = $this->fopen($path, 'w');
1036
+        [$count, $result] = \OC_Helper::streamCopy($stream, $target);
1037
+        fclose($target);
1038
+        return $count;
1039
+    }
1040 1040
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Wrapper.php 1 patch
Indentation   +600 added lines, -600 removed lines patch added patch discarded remove patch
@@ -39,604 +39,604 @@
 block discarded – undo
39 39
 use OCP\Lock\ILockingProvider;
40 40
 
41 41
 class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
42
-	/**
43
-	 * @var \OC\Files\Storage\Storage $storage
44
-	 */
45
-	protected $storage;
46
-
47
-	public $cache;
48
-	public $scanner;
49
-	public $watcher;
50
-	public $propagator;
51
-	public $updater;
52
-
53
-	/**
54
-	 * @param array $parameters
55
-	 */
56
-	public function __construct($parameters) {
57
-		$this->storage = $parameters['storage'];
58
-	}
59
-
60
-	/**
61
-	 * @return \OC\Files\Storage\Storage
62
-	 */
63
-	public function getWrapperStorage() {
64
-		return $this->storage;
65
-	}
66
-
67
-	/**
68
-	 * Get the identifier for the storage,
69
-	 * the returned id should be the same for every storage object that is created with the same parameters
70
-	 * and two storage objects with the same id should refer to two storages that display the same files.
71
-	 *
72
-	 * @return string
73
-	 */
74
-	public function getId() {
75
-		return $this->getWrapperStorage()->getId();
76
-	}
77
-
78
-	/**
79
-	 * see https://www.php.net/manual/en/function.mkdir.php
80
-	 *
81
-	 * @param string $path
82
-	 * @return bool
83
-	 */
84
-	public function mkdir($path) {
85
-		return $this->getWrapperStorage()->mkdir($path);
86
-	}
87
-
88
-	/**
89
-	 * see https://www.php.net/manual/en/function.rmdir.php
90
-	 *
91
-	 * @param string $path
92
-	 * @return bool
93
-	 */
94
-	public function rmdir($path) {
95
-		return $this->getWrapperStorage()->rmdir($path);
96
-	}
97
-
98
-	/**
99
-	 * see https://www.php.net/manual/en/function.opendir.php
100
-	 *
101
-	 * @param string $path
102
-	 * @return resource|bool
103
-	 */
104
-	public function opendir($path) {
105
-		return $this->getWrapperStorage()->opendir($path);
106
-	}
107
-
108
-	/**
109
-	 * see https://www.php.net/manual/en/function.is_dir.php
110
-	 *
111
-	 * @param string $path
112
-	 * @return bool
113
-	 */
114
-	public function is_dir($path) {
115
-		return $this->getWrapperStorage()->is_dir($path);
116
-	}
117
-
118
-	/**
119
-	 * see https://www.php.net/manual/en/function.is_file.php
120
-	 *
121
-	 * @param string $path
122
-	 * @return bool
123
-	 */
124
-	public function is_file($path) {
125
-		return $this->getWrapperStorage()->is_file($path);
126
-	}
127
-
128
-	/**
129
-	 * see https://www.php.net/manual/en/function.stat.php
130
-	 * only the following keys are required in the result: size and mtime
131
-	 *
132
-	 * @param string $path
133
-	 * @return array|bool
134
-	 */
135
-	public function stat($path) {
136
-		return $this->getWrapperStorage()->stat($path);
137
-	}
138
-
139
-	/**
140
-	 * see https://www.php.net/manual/en/function.filetype.php
141
-	 *
142
-	 * @param string $path
143
-	 * @return string|bool
144
-	 */
145
-	public function filetype($path) {
146
-		return $this->getWrapperStorage()->filetype($path);
147
-	}
148
-
149
-	/**
150
-	 * see https://www.php.net/manual/en/function.filesize.php
151
-	 * The result for filesize when called on a folder is required to be 0
152
-	 *
153
-	 * @param string $path
154
-	 * @return int|bool
155
-	 */
156
-	public function filesize($path) {
157
-		return $this->getWrapperStorage()->filesize($path);
158
-	}
159
-
160
-	/**
161
-	 * check if a file can be created in $path
162
-	 *
163
-	 * @param string $path
164
-	 * @return bool
165
-	 */
166
-	public function isCreatable($path) {
167
-		return $this->getWrapperStorage()->isCreatable($path);
168
-	}
169
-
170
-	/**
171
-	 * check if a file can be read
172
-	 *
173
-	 * @param string $path
174
-	 * @return bool
175
-	 */
176
-	public function isReadable($path) {
177
-		return $this->getWrapperStorage()->isReadable($path);
178
-	}
179
-
180
-	/**
181
-	 * check if a file can be written to
182
-	 *
183
-	 * @param string $path
184
-	 * @return bool
185
-	 */
186
-	public function isUpdatable($path) {
187
-		return $this->getWrapperStorage()->isUpdatable($path);
188
-	}
189
-
190
-	/**
191
-	 * check if a file can be deleted
192
-	 *
193
-	 * @param string $path
194
-	 * @return bool
195
-	 */
196
-	public function isDeletable($path) {
197
-		return $this->getWrapperStorage()->isDeletable($path);
198
-	}
199
-
200
-	/**
201
-	 * check if a file can be shared
202
-	 *
203
-	 * @param string $path
204
-	 * @return bool
205
-	 */
206
-	public function isSharable($path) {
207
-		return $this->getWrapperStorage()->isSharable($path);
208
-	}
209
-
210
-	/**
211
-	 * get the full permissions of a path.
212
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
213
-	 *
214
-	 * @param string $path
215
-	 * @return int
216
-	 */
217
-	public function getPermissions($path) {
218
-		return $this->getWrapperStorage()->getPermissions($path);
219
-	}
220
-
221
-	/**
222
-	 * see https://www.php.net/manual/en/function.file_exists.php
223
-	 *
224
-	 * @param string $path
225
-	 * @return bool
226
-	 */
227
-	public function file_exists($path) {
228
-		return $this->getWrapperStorage()->file_exists($path);
229
-	}
230
-
231
-	/**
232
-	 * see https://www.php.net/manual/en/function.filemtime.php
233
-	 *
234
-	 * @param string $path
235
-	 * @return int|bool
236
-	 */
237
-	public function filemtime($path) {
238
-		return $this->getWrapperStorage()->filemtime($path);
239
-	}
240
-
241
-	/**
242
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
243
-	 *
244
-	 * @param string $path
245
-	 * @return string|bool
246
-	 */
247
-	public function file_get_contents($path) {
248
-		return $this->getWrapperStorage()->file_get_contents($path);
249
-	}
250
-
251
-	/**
252
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
253
-	 *
254
-	 * @param string $path
255
-	 * @param mixed $data
256
-	 * @return int|false
257
-	 */
258
-	public function file_put_contents($path, $data) {
259
-		return $this->getWrapperStorage()->file_put_contents($path, $data);
260
-	}
261
-
262
-	/**
263
-	 * see https://www.php.net/manual/en/function.unlink.php
264
-	 *
265
-	 * @param string $path
266
-	 * @return bool
267
-	 */
268
-	public function unlink($path) {
269
-		return $this->getWrapperStorage()->unlink($path);
270
-	}
271
-
272
-	/**
273
-	 * see https://www.php.net/manual/en/function.rename.php
274
-	 *
275
-	 * @param string $path1
276
-	 * @param string $path2
277
-	 * @return bool
278
-	 */
279
-	public function rename($path1, $path2) {
280
-		return $this->getWrapperStorage()->rename($path1, $path2);
281
-	}
282
-
283
-	/**
284
-	 * see https://www.php.net/manual/en/function.copy.php
285
-	 *
286
-	 * @param string $path1
287
-	 * @param string $path2
288
-	 * @return bool
289
-	 */
290
-	public function copy($path1, $path2) {
291
-		return $this->getWrapperStorage()->copy($path1, $path2);
292
-	}
293
-
294
-	/**
295
-	 * see https://www.php.net/manual/en/function.fopen.php
296
-	 *
297
-	 * @param string $path
298
-	 * @param string $mode
299
-	 * @return resource|bool
300
-	 */
301
-	public function fopen($path, $mode) {
302
-		return $this->getWrapperStorage()->fopen($path, $mode);
303
-	}
304
-
305
-	/**
306
-	 * get the mimetype for a file or folder
307
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
308
-	 *
309
-	 * @param string $path
310
-	 * @return string|bool
311
-	 */
312
-	public function getMimeType($path) {
313
-		return $this->getWrapperStorage()->getMimeType($path);
314
-	}
315
-
316
-	/**
317
-	 * see https://www.php.net/manual/en/function.hash.php
318
-	 *
319
-	 * @param string $type
320
-	 * @param string $path
321
-	 * @param bool $raw
322
-	 * @return string|bool
323
-	 */
324
-	public function hash($type, $path, $raw = false) {
325
-		return $this->getWrapperStorage()->hash($type, $path, $raw);
326
-	}
327
-
328
-	/**
329
-	 * see https://www.php.net/manual/en/function.free_space.php
330
-	 *
331
-	 * @param string $path
332
-	 * @return int|bool
333
-	 */
334
-	public function free_space($path) {
335
-		return $this->getWrapperStorage()->free_space($path);
336
-	}
337
-
338
-	/**
339
-	 * search for occurrences of $query in file names
340
-	 *
341
-	 * @param string $query
342
-	 * @return array|bool
343
-	 */
344
-	public function search($query) {
345
-		return $this->getWrapperStorage()->search($query);
346
-	}
347
-
348
-	/**
349
-	 * see https://www.php.net/manual/en/function.touch.php
350
-	 * If the backend does not support the operation, false should be returned
351
-	 *
352
-	 * @param string $path
353
-	 * @param int $mtime
354
-	 * @return bool
355
-	 */
356
-	public function touch($path, $mtime = null) {
357
-		return $this->getWrapperStorage()->touch($path, $mtime);
358
-	}
359
-
360
-	/**
361
-	 * get the path to a local version of the file.
362
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
363
-	 *
364
-	 * @param string $path
365
-	 * @return string|bool
366
-	 */
367
-	public function getLocalFile($path) {
368
-		return $this->getWrapperStorage()->getLocalFile($path);
369
-	}
370
-
371
-	/**
372
-	 * check if a file or folder has been updated since $time
373
-	 *
374
-	 * @param string $path
375
-	 * @param int $time
376
-	 * @return bool
377
-	 *
378
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
379
-	 * returning true for other changes in the folder is optional
380
-	 */
381
-	public function hasUpdated($path, $time) {
382
-		return $this->getWrapperStorage()->hasUpdated($path, $time);
383
-	}
384
-
385
-	/**
386
-	 * get a cache instance for the storage
387
-	 *
388
-	 * @param string $path
389
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
390
-	 * @return \OC\Files\Cache\Cache
391
-	 */
392
-	public function getCache($path = '', $storage = null) {
393
-		if (!$storage) {
394
-			$storage = $this;
395
-		}
396
-		return $this->getWrapperStorage()->getCache($path, $storage);
397
-	}
398
-
399
-	/**
400
-	 * get a scanner instance for the storage
401
-	 *
402
-	 * @param string $path
403
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
404
-	 * @return \OC\Files\Cache\Scanner
405
-	 */
406
-	public function getScanner($path = '', $storage = null) {
407
-		if (!$storage) {
408
-			$storage = $this;
409
-		}
410
-		return $this->getWrapperStorage()->getScanner($path, $storage);
411
-	}
412
-
413
-
414
-	/**
415
-	 * get the user id of the owner of a file or folder
416
-	 *
417
-	 * @param string $path
418
-	 * @return string
419
-	 */
420
-	public function getOwner($path) {
421
-		return $this->getWrapperStorage()->getOwner($path);
422
-	}
423
-
424
-	/**
425
-	 * get a watcher instance for the cache
426
-	 *
427
-	 * @param string $path
428
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
429
-	 * @return \OC\Files\Cache\Watcher
430
-	 */
431
-	public function getWatcher($path = '', $storage = null) {
432
-		if (!$storage) {
433
-			$storage = $this;
434
-		}
435
-		return $this->getWrapperStorage()->getWatcher($path, $storage);
436
-	}
437
-
438
-	public function getPropagator($storage = null) {
439
-		if (!$storage) {
440
-			$storage = $this;
441
-		}
442
-		return $this->getWrapperStorage()->getPropagator($storage);
443
-	}
444
-
445
-	public function getUpdater($storage = null) {
446
-		if (!$storage) {
447
-			$storage = $this;
448
-		}
449
-		return $this->getWrapperStorage()->getUpdater($storage);
450
-	}
451
-
452
-	/**
453
-	 * @return \OC\Files\Cache\Storage
454
-	 */
455
-	public function getStorageCache() {
456
-		return $this->getWrapperStorage()->getStorageCache();
457
-	}
458
-
459
-	/**
460
-	 * get the ETag for a file or folder
461
-	 *
462
-	 * @param string $path
463
-	 * @return string|bool
464
-	 */
465
-	public function getETag($path) {
466
-		return $this->getWrapperStorage()->getETag($path);
467
-	}
468
-
469
-	/**
470
-	 * Returns true
471
-	 *
472
-	 * @return true
473
-	 */
474
-	public function test() {
475
-		return $this->getWrapperStorage()->test();
476
-	}
477
-
478
-	/**
479
-	 * Returns the wrapped storage's value for isLocal()
480
-	 *
481
-	 * @return bool wrapped storage's isLocal() value
482
-	 */
483
-	public function isLocal() {
484
-		return $this->getWrapperStorage()->isLocal();
485
-	}
486
-
487
-	/**
488
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
489
-	 *
490
-	 * @param string $class
491
-	 * @return bool
492
-	 */
493
-	public function instanceOfStorage($class) {
494
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
495
-			// FIXME Temporary fix to keep existing checks working
496
-			$class = '\OCA\Files_Sharing\SharedStorage';
497
-		}
498
-		return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
499
-	}
500
-
501
-	/**
502
-	 * Pass any methods custom to specific storage implementations to the wrapped storage
503
-	 *
504
-	 * @param string $method
505
-	 * @param array $args
506
-	 * @return mixed
507
-	 */
508
-	public function __call($method, $args) {
509
-		return call_user_func_array([$this->getWrapperStorage(), $method], $args);
510
-	}
511
-
512
-	/**
513
-	 * A custom storage implementation can return an url for direct download of a give file.
514
-	 *
515
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
516
-	 *
517
-	 * @param string $path
518
-	 * @return array|bool
519
-	 */
520
-	public function getDirectDownload($path) {
521
-		return $this->getWrapperStorage()->getDirectDownload($path);
522
-	}
523
-
524
-	/**
525
-	 * Get availability of the storage
526
-	 *
527
-	 * @return array [ available, last_checked ]
528
-	 */
529
-	public function getAvailability() {
530
-		return $this->getWrapperStorage()->getAvailability();
531
-	}
532
-
533
-	/**
534
-	 * Set availability of the storage
535
-	 *
536
-	 * @param bool $isAvailable
537
-	 */
538
-	public function setAvailability($isAvailable) {
539
-		$this->getWrapperStorage()->setAvailability($isAvailable);
540
-	}
541
-
542
-	/**
543
-	 * @param string $path the path of the target folder
544
-	 * @param string $fileName the name of the file itself
545
-	 * @return void
546
-	 * @throws InvalidPathException
547
-	 */
548
-	public function verifyPath($path, $fileName) {
549
-		$this->getWrapperStorage()->verifyPath($path, $fileName);
550
-	}
551
-
552
-	/**
553
-	 * @param IStorage $sourceStorage
554
-	 * @param string $sourceInternalPath
555
-	 * @param string $targetInternalPath
556
-	 * @return bool
557
-	 */
558
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
559
-		if ($sourceStorage === $this) {
560
-			return $this->copy($sourceInternalPath, $targetInternalPath);
561
-		}
562
-
563
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
564
-	}
565
-
566
-	/**
567
-	 * @param IStorage $sourceStorage
568
-	 * @param string $sourceInternalPath
569
-	 * @param string $targetInternalPath
570
-	 * @return bool
571
-	 */
572
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
573
-		if ($sourceStorage === $this) {
574
-			return $this->rename($sourceInternalPath, $targetInternalPath);
575
-		}
576
-
577
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
578
-	}
579
-
580
-	public function getMetaData($path) {
581
-		return $this->getWrapperStorage()->getMetaData($path);
582
-	}
583
-
584
-	/**
585
-	 * @param string $path
586
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
587
-	 * @param \OCP\Lock\ILockingProvider $provider
588
-	 * @throws \OCP\Lock\LockedException
589
-	 */
590
-	public function acquireLock($path, $type, ILockingProvider $provider) {
591
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
592
-			$this->getWrapperStorage()->acquireLock($path, $type, $provider);
593
-		}
594
-	}
595
-
596
-	/**
597
-	 * @param string $path
598
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
599
-	 * @param \OCP\Lock\ILockingProvider $provider
600
-	 */
601
-	public function releaseLock($path, $type, ILockingProvider $provider) {
602
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
603
-			$this->getWrapperStorage()->releaseLock($path, $type, $provider);
604
-		}
605
-	}
606
-
607
-	/**
608
-	 * @param string $path
609
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
610
-	 * @param \OCP\Lock\ILockingProvider $provider
611
-	 */
612
-	public function changeLock($path, $type, ILockingProvider $provider) {
613
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
614
-			$this->getWrapperStorage()->changeLock($path, $type, $provider);
615
-		}
616
-	}
617
-
618
-	/**
619
-	 * @return bool
620
-	 */
621
-	public function needsPartFile() {
622
-		return $this->getWrapperStorage()->needsPartFile();
623
-	}
624
-
625
-	public function writeStream(string $path, $stream, int $size = null): int {
626
-		$storage = $this->getWrapperStorage();
627
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
628
-			/** @var IWriteStreamStorage $storage */
629
-			return $storage->writeStream($path, $stream, $size);
630
-		} else {
631
-			$target = $this->fopen($path, 'w');
632
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
633
-			fclose($stream);
634
-			fclose($target);
635
-			return $count;
636
-		}
637
-	}
638
-
639
-	public function getDirectoryContent($directory): \Traversable {
640
-		return $this->getWrapperStorage()->getDirectoryContent($directory);
641
-	}
42
+    /**
43
+     * @var \OC\Files\Storage\Storage $storage
44
+     */
45
+    protected $storage;
46
+
47
+    public $cache;
48
+    public $scanner;
49
+    public $watcher;
50
+    public $propagator;
51
+    public $updater;
52
+
53
+    /**
54
+     * @param array $parameters
55
+     */
56
+    public function __construct($parameters) {
57
+        $this->storage = $parameters['storage'];
58
+    }
59
+
60
+    /**
61
+     * @return \OC\Files\Storage\Storage
62
+     */
63
+    public function getWrapperStorage() {
64
+        return $this->storage;
65
+    }
66
+
67
+    /**
68
+     * Get the identifier for the storage,
69
+     * the returned id should be the same for every storage object that is created with the same parameters
70
+     * and two storage objects with the same id should refer to two storages that display the same files.
71
+     *
72
+     * @return string
73
+     */
74
+    public function getId() {
75
+        return $this->getWrapperStorage()->getId();
76
+    }
77
+
78
+    /**
79
+     * see https://www.php.net/manual/en/function.mkdir.php
80
+     *
81
+     * @param string $path
82
+     * @return bool
83
+     */
84
+    public function mkdir($path) {
85
+        return $this->getWrapperStorage()->mkdir($path);
86
+    }
87
+
88
+    /**
89
+     * see https://www.php.net/manual/en/function.rmdir.php
90
+     *
91
+     * @param string $path
92
+     * @return bool
93
+     */
94
+    public function rmdir($path) {
95
+        return $this->getWrapperStorage()->rmdir($path);
96
+    }
97
+
98
+    /**
99
+     * see https://www.php.net/manual/en/function.opendir.php
100
+     *
101
+     * @param string $path
102
+     * @return resource|bool
103
+     */
104
+    public function opendir($path) {
105
+        return $this->getWrapperStorage()->opendir($path);
106
+    }
107
+
108
+    /**
109
+     * see https://www.php.net/manual/en/function.is_dir.php
110
+     *
111
+     * @param string $path
112
+     * @return bool
113
+     */
114
+    public function is_dir($path) {
115
+        return $this->getWrapperStorage()->is_dir($path);
116
+    }
117
+
118
+    /**
119
+     * see https://www.php.net/manual/en/function.is_file.php
120
+     *
121
+     * @param string $path
122
+     * @return bool
123
+     */
124
+    public function is_file($path) {
125
+        return $this->getWrapperStorage()->is_file($path);
126
+    }
127
+
128
+    /**
129
+     * see https://www.php.net/manual/en/function.stat.php
130
+     * only the following keys are required in the result: size and mtime
131
+     *
132
+     * @param string $path
133
+     * @return array|bool
134
+     */
135
+    public function stat($path) {
136
+        return $this->getWrapperStorage()->stat($path);
137
+    }
138
+
139
+    /**
140
+     * see https://www.php.net/manual/en/function.filetype.php
141
+     *
142
+     * @param string $path
143
+     * @return string|bool
144
+     */
145
+    public function filetype($path) {
146
+        return $this->getWrapperStorage()->filetype($path);
147
+    }
148
+
149
+    /**
150
+     * see https://www.php.net/manual/en/function.filesize.php
151
+     * The result for filesize when called on a folder is required to be 0
152
+     *
153
+     * @param string $path
154
+     * @return int|bool
155
+     */
156
+    public function filesize($path) {
157
+        return $this->getWrapperStorage()->filesize($path);
158
+    }
159
+
160
+    /**
161
+     * check if a file can be created in $path
162
+     *
163
+     * @param string $path
164
+     * @return bool
165
+     */
166
+    public function isCreatable($path) {
167
+        return $this->getWrapperStorage()->isCreatable($path);
168
+    }
169
+
170
+    /**
171
+     * check if a file can be read
172
+     *
173
+     * @param string $path
174
+     * @return bool
175
+     */
176
+    public function isReadable($path) {
177
+        return $this->getWrapperStorage()->isReadable($path);
178
+    }
179
+
180
+    /**
181
+     * check if a file can be written to
182
+     *
183
+     * @param string $path
184
+     * @return bool
185
+     */
186
+    public function isUpdatable($path) {
187
+        return $this->getWrapperStorage()->isUpdatable($path);
188
+    }
189
+
190
+    /**
191
+     * check if a file can be deleted
192
+     *
193
+     * @param string $path
194
+     * @return bool
195
+     */
196
+    public function isDeletable($path) {
197
+        return $this->getWrapperStorage()->isDeletable($path);
198
+    }
199
+
200
+    /**
201
+     * check if a file can be shared
202
+     *
203
+     * @param string $path
204
+     * @return bool
205
+     */
206
+    public function isSharable($path) {
207
+        return $this->getWrapperStorage()->isSharable($path);
208
+    }
209
+
210
+    /**
211
+     * get the full permissions of a path.
212
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
213
+     *
214
+     * @param string $path
215
+     * @return int
216
+     */
217
+    public function getPermissions($path) {
218
+        return $this->getWrapperStorage()->getPermissions($path);
219
+    }
220
+
221
+    /**
222
+     * see https://www.php.net/manual/en/function.file_exists.php
223
+     *
224
+     * @param string $path
225
+     * @return bool
226
+     */
227
+    public function file_exists($path) {
228
+        return $this->getWrapperStorage()->file_exists($path);
229
+    }
230
+
231
+    /**
232
+     * see https://www.php.net/manual/en/function.filemtime.php
233
+     *
234
+     * @param string $path
235
+     * @return int|bool
236
+     */
237
+    public function filemtime($path) {
238
+        return $this->getWrapperStorage()->filemtime($path);
239
+    }
240
+
241
+    /**
242
+     * see https://www.php.net/manual/en/function.file_get_contents.php
243
+     *
244
+     * @param string $path
245
+     * @return string|bool
246
+     */
247
+    public function file_get_contents($path) {
248
+        return $this->getWrapperStorage()->file_get_contents($path);
249
+    }
250
+
251
+    /**
252
+     * see https://www.php.net/manual/en/function.file_put_contents.php
253
+     *
254
+     * @param string $path
255
+     * @param mixed $data
256
+     * @return int|false
257
+     */
258
+    public function file_put_contents($path, $data) {
259
+        return $this->getWrapperStorage()->file_put_contents($path, $data);
260
+    }
261
+
262
+    /**
263
+     * see https://www.php.net/manual/en/function.unlink.php
264
+     *
265
+     * @param string $path
266
+     * @return bool
267
+     */
268
+    public function unlink($path) {
269
+        return $this->getWrapperStorage()->unlink($path);
270
+    }
271
+
272
+    /**
273
+     * see https://www.php.net/manual/en/function.rename.php
274
+     *
275
+     * @param string $path1
276
+     * @param string $path2
277
+     * @return bool
278
+     */
279
+    public function rename($path1, $path2) {
280
+        return $this->getWrapperStorage()->rename($path1, $path2);
281
+    }
282
+
283
+    /**
284
+     * see https://www.php.net/manual/en/function.copy.php
285
+     *
286
+     * @param string $path1
287
+     * @param string $path2
288
+     * @return bool
289
+     */
290
+    public function copy($path1, $path2) {
291
+        return $this->getWrapperStorage()->copy($path1, $path2);
292
+    }
293
+
294
+    /**
295
+     * see https://www.php.net/manual/en/function.fopen.php
296
+     *
297
+     * @param string $path
298
+     * @param string $mode
299
+     * @return resource|bool
300
+     */
301
+    public function fopen($path, $mode) {
302
+        return $this->getWrapperStorage()->fopen($path, $mode);
303
+    }
304
+
305
+    /**
306
+     * get the mimetype for a file or folder
307
+     * The mimetype for a folder is required to be "httpd/unix-directory"
308
+     *
309
+     * @param string $path
310
+     * @return string|bool
311
+     */
312
+    public function getMimeType($path) {
313
+        return $this->getWrapperStorage()->getMimeType($path);
314
+    }
315
+
316
+    /**
317
+     * see https://www.php.net/manual/en/function.hash.php
318
+     *
319
+     * @param string $type
320
+     * @param string $path
321
+     * @param bool $raw
322
+     * @return string|bool
323
+     */
324
+    public function hash($type, $path, $raw = false) {
325
+        return $this->getWrapperStorage()->hash($type, $path, $raw);
326
+    }
327
+
328
+    /**
329
+     * see https://www.php.net/manual/en/function.free_space.php
330
+     *
331
+     * @param string $path
332
+     * @return int|bool
333
+     */
334
+    public function free_space($path) {
335
+        return $this->getWrapperStorage()->free_space($path);
336
+    }
337
+
338
+    /**
339
+     * search for occurrences of $query in file names
340
+     *
341
+     * @param string $query
342
+     * @return array|bool
343
+     */
344
+    public function search($query) {
345
+        return $this->getWrapperStorage()->search($query);
346
+    }
347
+
348
+    /**
349
+     * see https://www.php.net/manual/en/function.touch.php
350
+     * If the backend does not support the operation, false should be returned
351
+     *
352
+     * @param string $path
353
+     * @param int $mtime
354
+     * @return bool
355
+     */
356
+    public function touch($path, $mtime = null) {
357
+        return $this->getWrapperStorage()->touch($path, $mtime);
358
+    }
359
+
360
+    /**
361
+     * get the path to a local version of the file.
362
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
363
+     *
364
+     * @param string $path
365
+     * @return string|bool
366
+     */
367
+    public function getLocalFile($path) {
368
+        return $this->getWrapperStorage()->getLocalFile($path);
369
+    }
370
+
371
+    /**
372
+     * check if a file or folder has been updated since $time
373
+     *
374
+     * @param string $path
375
+     * @param int $time
376
+     * @return bool
377
+     *
378
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
379
+     * returning true for other changes in the folder is optional
380
+     */
381
+    public function hasUpdated($path, $time) {
382
+        return $this->getWrapperStorage()->hasUpdated($path, $time);
383
+    }
384
+
385
+    /**
386
+     * get a cache instance for the storage
387
+     *
388
+     * @param string $path
389
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
390
+     * @return \OC\Files\Cache\Cache
391
+     */
392
+    public function getCache($path = '', $storage = null) {
393
+        if (!$storage) {
394
+            $storage = $this;
395
+        }
396
+        return $this->getWrapperStorage()->getCache($path, $storage);
397
+    }
398
+
399
+    /**
400
+     * get a scanner instance for the storage
401
+     *
402
+     * @param string $path
403
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
404
+     * @return \OC\Files\Cache\Scanner
405
+     */
406
+    public function getScanner($path = '', $storage = null) {
407
+        if (!$storage) {
408
+            $storage = $this;
409
+        }
410
+        return $this->getWrapperStorage()->getScanner($path, $storage);
411
+    }
412
+
413
+
414
+    /**
415
+     * get the user id of the owner of a file or folder
416
+     *
417
+     * @param string $path
418
+     * @return string
419
+     */
420
+    public function getOwner($path) {
421
+        return $this->getWrapperStorage()->getOwner($path);
422
+    }
423
+
424
+    /**
425
+     * get a watcher instance for the cache
426
+     *
427
+     * @param string $path
428
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
429
+     * @return \OC\Files\Cache\Watcher
430
+     */
431
+    public function getWatcher($path = '', $storage = null) {
432
+        if (!$storage) {
433
+            $storage = $this;
434
+        }
435
+        return $this->getWrapperStorage()->getWatcher($path, $storage);
436
+    }
437
+
438
+    public function getPropagator($storage = null) {
439
+        if (!$storage) {
440
+            $storage = $this;
441
+        }
442
+        return $this->getWrapperStorage()->getPropagator($storage);
443
+    }
444
+
445
+    public function getUpdater($storage = null) {
446
+        if (!$storage) {
447
+            $storage = $this;
448
+        }
449
+        return $this->getWrapperStorage()->getUpdater($storage);
450
+    }
451
+
452
+    /**
453
+     * @return \OC\Files\Cache\Storage
454
+     */
455
+    public function getStorageCache() {
456
+        return $this->getWrapperStorage()->getStorageCache();
457
+    }
458
+
459
+    /**
460
+     * get the ETag for a file or folder
461
+     *
462
+     * @param string $path
463
+     * @return string|bool
464
+     */
465
+    public function getETag($path) {
466
+        return $this->getWrapperStorage()->getETag($path);
467
+    }
468
+
469
+    /**
470
+     * Returns true
471
+     *
472
+     * @return true
473
+     */
474
+    public function test() {
475
+        return $this->getWrapperStorage()->test();
476
+    }
477
+
478
+    /**
479
+     * Returns the wrapped storage's value for isLocal()
480
+     *
481
+     * @return bool wrapped storage's isLocal() value
482
+     */
483
+    public function isLocal() {
484
+        return $this->getWrapperStorage()->isLocal();
485
+    }
486
+
487
+    /**
488
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
489
+     *
490
+     * @param string $class
491
+     * @return bool
492
+     */
493
+    public function instanceOfStorage($class) {
494
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
495
+            // FIXME Temporary fix to keep existing checks working
496
+            $class = '\OCA\Files_Sharing\SharedStorage';
497
+        }
498
+        return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
499
+    }
500
+
501
+    /**
502
+     * Pass any methods custom to specific storage implementations to the wrapped storage
503
+     *
504
+     * @param string $method
505
+     * @param array $args
506
+     * @return mixed
507
+     */
508
+    public function __call($method, $args) {
509
+        return call_user_func_array([$this->getWrapperStorage(), $method], $args);
510
+    }
511
+
512
+    /**
513
+     * A custom storage implementation can return an url for direct download of a give file.
514
+     *
515
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
516
+     *
517
+     * @param string $path
518
+     * @return array|bool
519
+     */
520
+    public function getDirectDownload($path) {
521
+        return $this->getWrapperStorage()->getDirectDownload($path);
522
+    }
523
+
524
+    /**
525
+     * Get availability of the storage
526
+     *
527
+     * @return array [ available, last_checked ]
528
+     */
529
+    public function getAvailability() {
530
+        return $this->getWrapperStorage()->getAvailability();
531
+    }
532
+
533
+    /**
534
+     * Set availability of the storage
535
+     *
536
+     * @param bool $isAvailable
537
+     */
538
+    public function setAvailability($isAvailable) {
539
+        $this->getWrapperStorage()->setAvailability($isAvailable);
540
+    }
541
+
542
+    /**
543
+     * @param string $path the path of the target folder
544
+     * @param string $fileName the name of the file itself
545
+     * @return void
546
+     * @throws InvalidPathException
547
+     */
548
+    public function verifyPath($path, $fileName) {
549
+        $this->getWrapperStorage()->verifyPath($path, $fileName);
550
+    }
551
+
552
+    /**
553
+     * @param IStorage $sourceStorage
554
+     * @param string $sourceInternalPath
555
+     * @param string $targetInternalPath
556
+     * @return bool
557
+     */
558
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
559
+        if ($sourceStorage === $this) {
560
+            return $this->copy($sourceInternalPath, $targetInternalPath);
561
+        }
562
+
563
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
564
+    }
565
+
566
+    /**
567
+     * @param IStorage $sourceStorage
568
+     * @param string $sourceInternalPath
569
+     * @param string $targetInternalPath
570
+     * @return bool
571
+     */
572
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
573
+        if ($sourceStorage === $this) {
574
+            return $this->rename($sourceInternalPath, $targetInternalPath);
575
+        }
576
+
577
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
578
+    }
579
+
580
+    public function getMetaData($path) {
581
+        return $this->getWrapperStorage()->getMetaData($path);
582
+    }
583
+
584
+    /**
585
+     * @param string $path
586
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
587
+     * @param \OCP\Lock\ILockingProvider $provider
588
+     * @throws \OCP\Lock\LockedException
589
+     */
590
+    public function acquireLock($path, $type, ILockingProvider $provider) {
591
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
592
+            $this->getWrapperStorage()->acquireLock($path, $type, $provider);
593
+        }
594
+    }
595
+
596
+    /**
597
+     * @param string $path
598
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
599
+     * @param \OCP\Lock\ILockingProvider $provider
600
+     */
601
+    public function releaseLock($path, $type, ILockingProvider $provider) {
602
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
603
+            $this->getWrapperStorage()->releaseLock($path, $type, $provider);
604
+        }
605
+    }
606
+
607
+    /**
608
+     * @param string $path
609
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
610
+     * @param \OCP\Lock\ILockingProvider $provider
611
+     */
612
+    public function changeLock($path, $type, ILockingProvider $provider) {
613
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
614
+            $this->getWrapperStorage()->changeLock($path, $type, $provider);
615
+        }
616
+    }
617
+
618
+    /**
619
+     * @return bool
620
+     */
621
+    public function needsPartFile() {
622
+        return $this->getWrapperStorage()->needsPartFile();
623
+    }
624
+
625
+    public function writeStream(string $path, $stream, int $size = null): int {
626
+        $storage = $this->getWrapperStorage();
627
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
628
+            /** @var IWriteStreamStorage $storage */
629
+            return $storage->writeStream($path, $stream, $size);
630
+        } else {
631
+            $target = $this->fopen($path, 'w');
632
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
633
+            fclose($stream);
634
+            fclose($target);
635
+            return $count;
636
+        }
637
+    }
638
+
639
+    public function getDirectoryContent($directory): \Traversable {
640
+        return $this->getWrapperStorage()->getDirectoryContent($directory);
641
+    }
642 642
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Jail.php 1 patch
Indentation   +496 added lines, -496 removed lines patch added patch discarded remove patch
@@ -42,500 +42,500 @@
 block discarded – undo
42 42
  * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
43 43
  */
44 44
 class Jail extends Wrapper {
45
-	/**
46
-	 * @var string
47
-	 */
48
-	protected $rootPath;
49
-
50
-	/**
51
-	 * @param array $arguments ['storage' => $storage, 'mask' => $root]
52
-	 *
53
-	 * $storage: The storage that will be wrapper
54
-	 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
55
-	 */
56
-	public function __construct($arguments) {
57
-		parent::__construct($arguments);
58
-		$this->rootPath = $arguments['root'];
59
-	}
60
-
61
-	public function getUnjailedPath($path) {
62
-		return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
63
-	}
64
-
65
-	/**
66
-	 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
67
-	 */
68
-	public function getUnjailedStorage() {
69
-		return $this->storage;
70
-	}
71
-
72
-
73
-	public function getJailedPath($path) {
74
-		$root = rtrim($this->rootPath, '/') . '/';
75
-
76
-		if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
77
-			return null;
78
-		} else {
79
-			$path = substr($path, strlen($this->rootPath));
80
-			return trim($path, '/');
81
-		}
82
-	}
83
-
84
-	public function getId() {
85
-		return parent::getId();
86
-	}
87
-
88
-	/**
89
-	 * see https://www.php.net/manual/en/function.mkdir.php
90
-	 *
91
-	 * @param string $path
92
-	 * @return bool
93
-	 */
94
-	public function mkdir($path) {
95
-		return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
96
-	}
97
-
98
-	/**
99
-	 * see https://www.php.net/manual/en/function.rmdir.php
100
-	 *
101
-	 * @param string $path
102
-	 * @return bool
103
-	 */
104
-	public function rmdir($path) {
105
-		return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
106
-	}
107
-
108
-	/**
109
-	 * see https://www.php.net/manual/en/function.opendir.php
110
-	 *
111
-	 * @param string $path
112
-	 * @return resource|bool
113
-	 */
114
-	public function opendir($path) {
115
-		return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
116
-	}
117
-
118
-	/**
119
-	 * see https://www.php.net/manual/en/function.is_dir.php
120
-	 *
121
-	 * @param string $path
122
-	 * @return bool
123
-	 */
124
-	public function is_dir($path) {
125
-		return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
126
-	}
127
-
128
-	/**
129
-	 * see https://www.php.net/manual/en/function.is_file.php
130
-	 *
131
-	 * @param string $path
132
-	 * @return bool
133
-	 */
134
-	public function is_file($path) {
135
-		return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
136
-	}
137
-
138
-	/**
139
-	 * see https://www.php.net/manual/en/function.stat.php
140
-	 * only the following keys are required in the result: size and mtime
141
-	 *
142
-	 * @param string $path
143
-	 * @return array|bool
144
-	 */
145
-	public function stat($path) {
146
-		return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
147
-	}
148
-
149
-	/**
150
-	 * see https://www.php.net/manual/en/function.filetype.php
151
-	 *
152
-	 * @param string $path
153
-	 * @return bool
154
-	 */
155
-	public function filetype($path) {
156
-		return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
157
-	}
158
-
159
-	/**
160
-	 * see https://www.php.net/manual/en/function.filesize.php
161
-	 * The result for filesize when called on a folder is required to be 0
162
-	 *
163
-	 * @param string $path
164
-	 * @return int|bool
165
-	 */
166
-	public function filesize($path) {
167
-		return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
168
-	}
169
-
170
-	/**
171
-	 * check if a file can be created in $path
172
-	 *
173
-	 * @param string $path
174
-	 * @return bool
175
-	 */
176
-	public function isCreatable($path) {
177
-		return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
178
-	}
179
-
180
-	/**
181
-	 * check if a file can be read
182
-	 *
183
-	 * @param string $path
184
-	 * @return bool
185
-	 */
186
-	public function isReadable($path) {
187
-		return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
188
-	}
189
-
190
-	/**
191
-	 * check if a file can be written to
192
-	 *
193
-	 * @param string $path
194
-	 * @return bool
195
-	 */
196
-	public function isUpdatable($path) {
197
-		return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
198
-	}
199
-
200
-	/**
201
-	 * check if a file can be deleted
202
-	 *
203
-	 * @param string $path
204
-	 * @return bool
205
-	 */
206
-	public function isDeletable($path) {
207
-		return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
208
-	}
209
-
210
-	/**
211
-	 * check if a file can be shared
212
-	 *
213
-	 * @param string $path
214
-	 * @return bool
215
-	 */
216
-	public function isSharable($path) {
217
-		return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
218
-	}
219
-
220
-	/**
221
-	 * get the full permissions of a path.
222
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
223
-	 *
224
-	 * @param string $path
225
-	 * @return int
226
-	 */
227
-	public function getPermissions($path) {
228
-		return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
229
-	}
230
-
231
-	/**
232
-	 * see https://www.php.net/manual/en/function.file_exists.php
233
-	 *
234
-	 * @param string $path
235
-	 * @return bool
236
-	 */
237
-	public function file_exists($path) {
238
-		return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
239
-	}
240
-
241
-	/**
242
-	 * see https://www.php.net/manual/en/function.filemtime.php
243
-	 *
244
-	 * @param string $path
245
-	 * @return int|bool
246
-	 */
247
-	public function filemtime($path) {
248
-		return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
249
-	}
250
-
251
-	/**
252
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
253
-	 *
254
-	 * @param string $path
255
-	 * @return string|bool
256
-	 */
257
-	public function file_get_contents($path) {
258
-		return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
259
-	}
260
-
261
-	/**
262
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
263
-	 *
264
-	 * @param string $path
265
-	 * @param mixed $data
266
-	 * @return int|false
267
-	 */
268
-	public function file_put_contents($path, $data) {
269
-		return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
270
-	}
271
-
272
-	/**
273
-	 * see https://www.php.net/manual/en/function.unlink.php
274
-	 *
275
-	 * @param string $path
276
-	 * @return bool
277
-	 */
278
-	public function unlink($path) {
279
-		return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
280
-	}
281
-
282
-	/**
283
-	 * see https://www.php.net/manual/en/function.rename.php
284
-	 *
285
-	 * @param string $path1
286
-	 * @param string $path2
287
-	 * @return bool
288
-	 */
289
-	public function rename($path1, $path2) {
290
-		return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
291
-	}
292
-
293
-	/**
294
-	 * see https://www.php.net/manual/en/function.copy.php
295
-	 *
296
-	 * @param string $path1
297
-	 * @param string $path2
298
-	 * @return bool
299
-	 */
300
-	public function copy($path1, $path2) {
301
-		return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
302
-	}
303
-
304
-	/**
305
-	 * see https://www.php.net/manual/en/function.fopen.php
306
-	 *
307
-	 * @param string $path
308
-	 * @param string $mode
309
-	 * @return resource|bool
310
-	 */
311
-	public function fopen($path, $mode) {
312
-		return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
313
-	}
314
-
315
-	/**
316
-	 * get the mimetype for a file or folder
317
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
318
-	 *
319
-	 * @param string $path
320
-	 * @return string|bool
321
-	 */
322
-	public function getMimeType($path) {
323
-		return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
324
-	}
325
-
326
-	/**
327
-	 * see https://www.php.net/manual/en/function.hash.php
328
-	 *
329
-	 * @param string $type
330
-	 * @param string $path
331
-	 * @param bool $raw
332
-	 * @return string|bool
333
-	 */
334
-	public function hash($type, $path, $raw = false) {
335
-		return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
336
-	}
337
-
338
-	/**
339
-	 * see https://www.php.net/manual/en/function.free_space.php
340
-	 *
341
-	 * @param string $path
342
-	 * @return int|bool
343
-	 */
344
-	public function free_space($path) {
345
-		return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
346
-	}
347
-
348
-	/**
349
-	 * search for occurrences of $query in file names
350
-	 *
351
-	 * @param string $query
352
-	 * @return array|bool
353
-	 */
354
-	public function search($query) {
355
-		return $this->getWrapperStorage()->search($query);
356
-	}
357
-
358
-	/**
359
-	 * see https://www.php.net/manual/en/function.touch.php
360
-	 * If the backend does not support the operation, false should be returned
361
-	 *
362
-	 * @param string $path
363
-	 * @param int $mtime
364
-	 * @return bool
365
-	 */
366
-	public function touch($path, $mtime = null) {
367
-		return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
368
-	}
369
-
370
-	/**
371
-	 * get the path to a local version of the file.
372
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
373
-	 *
374
-	 * @param string $path
375
-	 * @return string|bool
376
-	 */
377
-	public function getLocalFile($path) {
378
-		return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
379
-	}
380
-
381
-	/**
382
-	 * check if a file or folder has been updated since $time
383
-	 *
384
-	 * @param string $path
385
-	 * @param int $time
386
-	 * @return bool
387
-	 *
388
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
389
-	 * returning true for other changes in the folder is optional
390
-	 */
391
-	public function hasUpdated($path, $time) {
392
-		return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
393
-	}
394
-
395
-	/**
396
-	 * get a cache instance for the storage
397
-	 *
398
-	 * @param string $path
399
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
400
-	 * @return \OC\Files\Cache\Cache
401
-	 */
402
-	public function getCache($path = '', $storage = null) {
403
-		if (!$storage) {
404
-			$storage = $this->getWrapperStorage();
405
-		}
406
-		$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
407
-		return new CacheJail($sourceCache, $this->rootPath);
408
-	}
409
-
410
-	/**
411
-	 * get the user id of the owner of a file or folder
412
-	 *
413
-	 * @param string $path
414
-	 * @return string
415
-	 */
416
-	public function getOwner($path) {
417
-		return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
418
-	}
419
-
420
-	/**
421
-	 * get a watcher instance for the cache
422
-	 *
423
-	 * @param string $path
424
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
425
-	 * @return \OC\Files\Cache\Watcher
426
-	 */
427
-	public function getWatcher($path = '', $storage = null) {
428
-		if (!$storage) {
429
-			$storage = $this;
430
-		}
431
-		return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
432
-	}
433
-
434
-	/**
435
-	 * get the ETag for a file or folder
436
-	 *
437
-	 * @param string $path
438
-	 * @return string|bool
439
-	 */
440
-	public function getETag($path) {
441
-		return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
442
-	}
443
-
444
-	public function getMetaData($path) {
445
-		return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
446
-	}
447
-
448
-	/**
449
-	 * @param string $path
450
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
451
-	 * @param \OCP\Lock\ILockingProvider $provider
452
-	 * @throws \OCP\Lock\LockedException
453
-	 */
454
-	public function acquireLock($path, $type, ILockingProvider $provider) {
455
-		$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
456
-	}
457
-
458
-	/**
459
-	 * @param string $path
460
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
461
-	 * @param \OCP\Lock\ILockingProvider $provider
462
-	 */
463
-	public function releaseLock($path, $type, ILockingProvider $provider) {
464
-		$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
465
-	}
466
-
467
-	/**
468
-	 * @param string $path
469
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
470
-	 * @param \OCP\Lock\ILockingProvider $provider
471
-	 */
472
-	public function changeLock($path, $type, ILockingProvider $provider) {
473
-		$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
474
-	}
475
-
476
-	/**
477
-	 * Resolve the path for the source of the share
478
-	 *
479
-	 * @param string $path
480
-	 * @return array
481
-	 */
482
-	public function resolvePath($path) {
483
-		return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
484
-	}
485
-
486
-	/**
487
-	 * @param IStorage $sourceStorage
488
-	 * @param string $sourceInternalPath
489
-	 * @param string $targetInternalPath
490
-	 * @return bool
491
-	 */
492
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
-		if ($sourceStorage === $this) {
494
-			return $this->copy($sourceInternalPath, $targetInternalPath);
495
-		}
496
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
497
-	}
498
-
499
-	/**
500
-	 * @param IStorage $sourceStorage
501
-	 * @param string $sourceInternalPath
502
-	 * @param string $targetInternalPath
503
-	 * @return bool
504
-	 */
505
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
506
-		if ($sourceStorage === $this) {
507
-			return $this->rename($sourceInternalPath, $targetInternalPath);
508
-		}
509
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
510
-	}
511
-
512
-	public function getPropagator($storage = null) {
513
-		if (isset($this->propagator)) {
514
-			return $this->propagator;
515
-		}
516
-
517
-		if (!$storage) {
518
-			$storage = $this;
519
-		}
520
-		$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
521
-		return $this->propagator;
522
-	}
523
-
524
-	public function writeStream(string $path, $stream, int $size = null): int {
525
-		$storage = $this->getWrapperStorage();
526
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
527
-			/** @var IWriteStreamStorage $storage */
528
-			return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
529
-		} else {
530
-			$target = $this->fopen($path, 'w');
531
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
532
-			fclose($stream);
533
-			fclose($target);
534
-			return $count;
535
-		}
536
-	}
537
-
538
-	public function getDirectoryContent($directory): \Traversable {
539
-		return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
540
-	}
45
+    /**
46
+     * @var string
47
+     */
48
+    protected $rootPath;
49
+
50
+    /**
51
+     * @param array $arguments ['storage' => $storage, 'mask' => $root]
52
+     *
53
+     * $storage: The storage that will be wrapper
54
+     * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
55
+     */
56
+    public function __construct($arguments) {
57
+        parent::__construct($arguments);
58
+        $this->rootPath = $arguments['root'];
59
+    }
60
+
61
+    public function getUnjailedPath($path) {
62
+        return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
63
+    }
64
+
65
+    /**
66
+     * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
67
+     */
68
+    public function getUnjailedStorage() {
69
+        return $this->storage;
70
+    }
71
+
72
+
73
+    public function getJailedPath($path) {
74
+        $root = rtrim($this->rootPath, '/') . '/';
75
+
76
+        if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
77
+            return null;
78
+        } else {
79
+            $path = substr($path, strlen($this->rootPath));
80
+            return trim($path, '/');
81
+        }
82
+    }
83
+
84
+    public function getId() {
85
+        return parent::getId();
86
+    }
87
+
88
+    /**
89
+     * see https://www.php.net/manual/en/function.mkdir.php
90
+     *
91
+     * @param string $path
92
+     * @return bool
93
+     */
94
+    public function mkdir($path) {
95
+        return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
96
+    }
97
+
98
+    /**
99
+     * see https://www.php.net/manual/en/function.rmdir.php
100
+     *
101
+     * @param string $path
102
+     * @return bool
103
+     */
104
+    public function rmdir($path) {
105
+        return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
106
+    }
107
+
108
+    /**
109
+     * see https://www.php.net/manual/en/function.opendir.php
110
+     *
111
+     * @param string $path
112
+     * @return resource|bool
113
+     */
114
+    public function opendir($path) {
115
+        return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
116
+    }
117
+
118
+    /**
119
+     * see https://www.php.net/manual/en/function.is_dir.php
120
+     *
121
+     * @param string $path
122
+     * @return bool
123
+     */
124
+    public function is_dir($path) {
125
+        return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
126
+    }
127
+
128
+    /**
129
+     * see https://www.php.net/manual/en/function.is_file.php
130
+     *
131
+     * @param string $path
132
+     * @return bool
133
+     */
134
+    public function is_file($path) {
135
+        return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
136
+    }
137
+
138
+    /**
139
+     * see https://www.php.net/manual/en/function.stat.php
140
+     * only the following keys are required in the result: size and mtime
141
+     *
142
+     * @param string $path
143
+     * @return array|bool
144
+     */
145
+    public function stat($path) {
146
+        return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
147
+    }
148
+
149
+    /**
150
+     * see https://www.php.net/manual/en/function.filetype.php
151
+     *
152
+     * @param string $path
153
+     * @return bool
154
+     */
155
+    public function filetype($path) {
156
+        return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
157
+    }
158
+
159
+    /**
160
+     * see https://www.php.net/manual/en/function.filesize.php
161
+     * The result for filesize when called on a folder is required to be 0
162
+     *
163
+     * @param string $path
164
+     * @return int|bool
165
+     */
166
+    public function filesize($path) {
167
+        return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
168
+    }
169
+
170
+    /**
171
+     * check if a file can be created in $path
172
+     *
173
+     * @param string $path
174
+     * @return bool
175
+     */
176
+    public function isCreatable($path) {
177
+        return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
178
+    }
179
+
180
+    /**
181
+     * check if a file can be read
182
+     *
183
+     * @param string $path
184
+     * @return bool
185
+     */
186
+    public function isReadable($path) {
187
+        return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
188
+    }
189
+
190
+    /**
191
+     * check if a file can be written to
192
+     *
193
+     * @param string $path
194
+     * @return bool
195
+     */
196
+    public function isUpdatable($path) {
197
+        return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
198
+    }
199
+
200
+    /**
201
+     * check if a file can be deleted
202
+     *
203
+     * @param string $path
204
+     * @return bool
205
+     */
206
+    public function isDeletable($path) {
207
+        return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
208
+    }
209
+
210
+    /**
211
+     * check if a file can be shared
212
+     *
213
+     * @param string $path
214
+     * @return bool
215
+     */
216
+    public function isSharable($path) {
217
+        return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
218
+    }
219
+
220
+    /**
221
+     * get the full permissions of a path.
222
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
223
+     *
224
+     * @param string $path
225
+     * @return int
226
+     */
227
+    public function getPermissions($path) {
228
+        return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
229
+    }
230
+
231
+    /**
232
+     * see https://www.php.net/manual/en/function.file_exists.php
233
+     *
234
+     * @param string $path
235
+     * @return bool
236
+     */
237
+    public function file_exists($path) {
238
+        return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
239
+    }
240
+
241
+    /**
242
+     * see https://www.php.net/manual/en/function.filemtime.php
243
+     *
244
+     * @param string $path
245
+     * @return int|bool
246
+     */
247
+    public function filemtime($path) {
248
+        return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
249
+    }
250
+
251
+    /**
252
+     * see https://www.php.net/manual/en/function.file_get_contents.php
253
+     *
254
+     * @param string $path
255
+     * @return string|bool
256
+     */
257
+    public function file_get_contents($path) {
258
+        return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
259
+    }
260
+
261
+    /**
262
+     * see https://www.php.net/manual/en/function.file_put_contents.php
263
+     *
264
+     * @param string $path
265
+     * @param mixed $data
266
+     * @return int|false
267
+     */
268
+    public function file_put_contents($path, $data) {
269
+        return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
270
+    }
271
+
272
+    /**
273
+     * see https://www.php.net/manual/en/function.unlink.php
274
+     *
275
+     * @param string $path
276
+     * @return bool
277
+     */
278
+    public function unlink($path) {
279
+        return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
280
+    }
281
+
282
+    /**
283
+     * see https://www.php.net/manual/en/function.rename.php
284
+     *
285
+     * @param string $path1
286
+     * @param string $path2
287
+     * @return bool
288
+     */
289
+    public function rename($path1, $path2) {
290
+        return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
291
+    }
292
+
293
+    /**
294
+     * see https://www.php.net/manual/en/function.copy.php
295
+     *
296
+     * @param string $path1
297
+     * @param string $path2
298
+     * @return bool
299
+     */
300
+    public function copy($path1, $path2) {
301
+        return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
302
+    }
303
+
304
+    /**
305
+     * see https://www.php.net/manual/en/function.fopen.php
306
+     *
307
+     * @param string $path
308
+     * @param string $mode
309
+     * @return resource|bool
310
+     */
311
+    public function fopen($path, $mode) {
312
+        return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
313
+    }
314
+
315
+    /**
316
+     * get the mimetype for a file or folder
317
+     * The mimetype for a folder is required to be "httpd/unix-directory"
318
+     *
319
+     * @param string $path
320
+     * @return string|bool
321
+     */
322
+    public function getMimeType($path) {
323
+        return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
324
+    }
325
+
326
+    /**
327
+     * see https://www.php.net/manual/en/function.hash.php
328
+     *
329
+     * @param string $type
330
+     * @param string $path
331
+     * @param bool $raw
332
+     * @return string|bool
333
+     */
334
+    public function hash($type, $path, $raw = false) {
335
+        return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
336
+    }
337
+
338
+    /**
339
+     * see https://www.php.net/manual/en/function.free_space.php
340
+     *
341
+     * @param string $path
342
+     * @return int|bool
343
+     */
344
+    public function free_space($path) {
345
+        return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
346
+    }
347
+
348
+    /**
349
+     * search for occurrences of $query in file names
350
+     *
351
+     * @param string $query
352
+     * @return array|bool
353
+     */
354
+    public function search($query) {
355
+        return $this->getWrapperStorage()->search($query);
356
+    }
357
+
358
+    /**
359
+     * see https://www.php.net/manual/en/function.touch.php
360
+     * If the backend does not support the operation, false should be returned
361
+     *
362
+     * @param string $path
363
+     * @param int $mtime
364
+     * @return bool
365
+     */
366
+    public function touch($path, $mtime = null) {
367
+        return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
368
+    }
369
+
370
+    /**
371
+     * get the path to a local version of the file.
372
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
373
+     *
374
+     * @param string $path
375
+     * @return string|bool
376
+     */
377
+    public function getLocalFile($path) {
378
+        return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
379
+    }
380
+
381
+    /**
382
+     * check if a file or folder has been updated since $time
383
+     *
384
+     * @param string $path
385
+     * @param int $time
386
+     * @return bool
387
+     *
388
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
389
+     * returning true for other changes in the folder is optional
390
+     */
391
+    public function hasUpdated($path, $time) {
392
+        return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
393
+    }
394
+
395
+    /**
396
+     * get a cache instance for the storage
397
+     *
398
+     * @param string $path
399
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
400
+     * @return \OC\Files\Cache\Cache
401
+     */
402
+    public function getCache($path = '', $storage = null) {
403
+        if (!$storage) {
404
+            $storage = $this->getWrapperStorage();
405
+        }
406
+        $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
407
+        return new CacheJail($sourceCache, $this->rootPath);
408
+    }
409
+
410
+    /**
411
+     * get the user id of the owner of a file or folder
412
+     *
413
+     * @param string $path
414
+     * @return string
415
+     */
416
+    public function getOwner($path) {
417
+        return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
418
+    }
419
+
420
+    /**
421
+     * get a watcher instance for the cache
422
+     *
423
+     * @param string $path
424
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
425
+     * @return \OC\Files\Cache\Watcher
426
+     */
427
+    public function getWatcher($path = '', $storage = null) {
428
+        if (!$storage) {
429
+            $storage = $this;
430
+        }
431
+        return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
432
+    }
433
+
434
+    /**
435
+     * get the ETag for a file or folder
436
+     *
437
+     * @param string $path
438
+     * @return string|bool
439
+     */
440
+    public function getETag($path) {
441
+        return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
442
+    }
443
+
444
+    public function getMetaData($path) {
445
+        return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
446
+    }
447
+
448
+    /**
449
+     * @param string $path
450
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
451
+     * @param \OCP\Lock\ILockingProvider $provider
452
+     * @throws \OCP\Lock\LockedException
453
+     */
454
+    public function acquireLock($path, $type, ILockingProvider $provider) {
455
+        $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
456
+    }
457
+
458
+    /**
459
+     * @param string $path
460
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
461
+     * @param \OCP\Lock\ILockingProvider $provider
462
+     */
463
+    public function releaseLock($path, $type, ILockingProvider $provider) {
464
+        $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
465
+    }
466
+
467
+    /**
468
+     * @param string $path
469
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
470
+     * @param \OCP\Lock\ILockingProvider $provider
471
+     */
472
+    public function changeLock($path, $type, ILockingProvider $provider) {
473
+        $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
474
+    }
475
+
476
+    /**
477
+     * Resolve the path for the source of the share
478
+     *
479
+     * @param string $path
480
+     * @return array
481
+     */
482
+    public function resolvePath($path) {
483
+        return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
484
+    }
485
+
486
+    /**
487
+     * @param IStorage $sourceStorage
488
+     * @param string $sourceInternalPath
489
+     * @param string $targetInternalPath
490
+     * @return bool
491
+     */
492
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
+        if ($sourceStorage === $this) {
494
+            return $this->copy($sourceInternalPath, $targetInternalPath);
495
+        }
496
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
497
+    }
498
+
499
+    /**
500
+     * @param IStorage $sourceStorage
501
+     * @param string $sourceInternalPath
502
+     * @param string $targetInternalPath
503
+     * @return bool
504
+     */
505
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
506
+        if ($sourceStorage === $this) {
507
+            return $this->rename($sourceInternalPath, $targetInternalPath);
508
+        }
509
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
510
+    }
511
+
512
+    public function getPropagator($storage = null) {
513
+        if (isset($this->propagator)) {
514
+            return $this->propagator;
515
+        }
516
+
517
+        if (!$storage) {
518
+            $storage = $this;
519
+        }
520
+        $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
521
+        return $this->propagator;
522
+    }
523
+
524
+    public function writeStream(string $path, $stream, int $size = null): int {
525
+        $storage = $this->getWrapperStorage();
526
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
527
+            /** @var IWriteStreamStorage $storage */
528
+            return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
529
+        } else {
530
+            $target = $this->fopen($path, 'w');
531
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
532
+            fclose($stream);
533
+            fclose($target);
534
+            return $count;
535
+        }
536
+    }
537
+
538
+    public function getDirectoryContent($directory): \Traversable {
539
+        return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
540
+    }
541 541
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 1 patch
Indentation   +498 added lines, -498 removed lines patch added patch discarded remove patch
@@ -40,502 +40,502 @@
 block discarded – undo
40 40
  */
41 41
 class Encoding extends Wrapper {
42 42
 
43
-	/**
44
-	 * @var ICache
45
-	 */
46
-	private $namesCache;
47
-
48
-	/**
49
-	 * @param array $parameters
50
-	 */
51
-	public function __construct($parameters) {
52
-		$this->storage = $parameters['storage'];
53
-		$this->namesCache = new CappedMemoryCache();
54
-	}
55
-
56
-	/**
57
-	 * Returns whether the given string is only made of ASCII characters
58
-	 *
59
-	 * @param string $str string
60
-	 *
61
-	 * @return bool true if the string is all ASCII, false otherwise
62
-	 */
63
-	private function isAscii($str) {
64
-		return !preg_match('/[\\x80-\\xff]+/', $str);
65
-	}
66
-
67
-	/**
68
-	 * Checks whether the given path exists in NFC or NFD form after checking
69
-	 * each form for each path section and returns the correct form.
70
-	 * If no existing path found, returns the path as it was given.
71
-	 *
72
-	 * @param string $fullPath path to check
73
-	 *
74
-	 * @return string original or converted path
75
-	 */
76
-	private function findPathToUse($fullPath) {
77
-		$cachedPath = $this->namesCache[$fullPath];
78
-		if ($cachedPath !== null) {
79
-			return $cachedPath;
80
-		}
81
-
82
-		$sections = explode('/', $fullPath);
83
-		$path = '';
84
-		foreach ($sections as $section) {
85
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
86
-			if ($convertedPath === null) {
87
-				// no point in continuing if the section was not found, use original path
88
-				return $fullPath;
89
-			}
90
-			$path = $convertedPath . '/';
91
-		}
92
-		$path = rtrim($path, '/');
93
-		return $path;
94
-	}
95
-
96
-	/**
97
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
98
-	 * and returns the correct form. If no existing path found, returns null.
99
-	 *
100
-	 * @param string $basePath base path to check
101
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
102
-	 *
103
-	 * @return string|null original or converted path, or null if none of the forms was found
104
-	 */
105
-	private function findPathToUseLastSection($basePath, $lastSection) {
106
-		$fullPath = $basePath . $lastSection;
107
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
108
-			$this->namesCache[$fullPath] = $fullPath;
109
-			return $fullPath;
110
-		}
111
-
112
-		// swap encoding
113
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
114
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
115
-		} else {
116
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
117
-		}
118
-		$otherFullPath = $basePath . $otherFormPath;
119
-		if ($this->storage->file_exists($otherFullPath)) {
120
-			$this->namesCache[$fullPath] = $otherFullPath;
121
-			return $otherFullPath;
122
-		}
123
-
124
-		// return original path, file did not exist at all
125
-		$this->namesCache[$fullPath] = $fullPath;
126
-		return null;
127
-	}
128
-
129
-	/**
130
-	 * see https://www.php.net/manual/en/function.mkdir.php
131
-	 *
132
-	 * @param string $path
133
-	 * @return bool
134
-	 */
135
-	public function mkdir($path) {
136
-		// note: no conversion here, method should not be called with non-NFC names!
137
-		$result = $this->storage->mkdir($path);
138
-		if ($result) {
139
-			$this->namesCache[$path] = $path;
140
-		}
141
-		return $result;
142
-	}
143
-
144
-	/**
145
-	 * see https://www.php.net/manual/en/function.rmdir.php
146
-	 *
147
-	 * @param string $path
148
-	 * @return bool
149
-	 */
150
-	public function rmdir($path) {
151
-		$result = $this->storage->rmdir($this->findPathToUse($path));
152
-		if ($result) {
153
-			unset($this->namesCache[$path]);
154
-		}
155
-		return $result;
156
-	}
157
-
158
-	/**
159
-	 * see https://www.php.net/manual/en/function.opendir.php
160
-	 *
161
-	 * @param string $path
162
-	 * @return resource|bool
163
-	 */
164
-	public function opendir($path) {
165
-		return $this->storage->opendir($this->findPathToUse($path));
166
-	}
167
-
168
-	/**
169
-	 * see https://www.php.net/manual/en/function.is_dir.php
170
-	 *
171
-	 * @param string $path
172
-	 * @return bool
173
-	 */
174
-	public function is_dir($path) {
175
-		return $this->storage->is_dir($this->findPathToUse($path));
176
-	}
177
-
178
-	/**
179
-	 * see https://www.php.net/manual/en/function.is_file.php
180
-	 *
181
-	 * @param string $path
182
-	 * @return bool
183
-	 */
184
-	public function is_file($path) {
185
-		return $this->storage->is_file($this->findPathToUse($path));
186
-	}
187
-
188
-	/**
189
-	 * see https://www.php.net/manual/en/function.stat.php
190
-	 * only the following keys are required in the result: size and mtime
191
-	 *
192
-	 * @param string $path
193
-	 * @return array|bool
194
-	 */
195
-	public function stat($path) {
196
-		return $this->storage->stat($this->findPathToUse($path));
197
-	}
198
-
199
-	/**
200
-	 * see https://www.php.net/manual/en/function.filetype.php
201
-	 *
202
-	 * @param string $path
203
-	 * @return string|bool
204
-	 */
205
-	public function filetype($path) {
206
-		return $this->storage->filetype($this->findPathToUse($path));
207
-	}
208
-
209
-	/**
210
-	 * see https://www.php.net/manual/en/function.filesize.php
211
-	 * The result for filesize when called on a folder is required to be 0
212
-	 *
213
-	 * @param string $path
214
-	 * @return int|bool
215
-	 */
216
-	public function filesize($path) {
217
-		return $this->storage->filesize($this->findPathToUse($path));
218
-	}
219
-
220
-	/**
221
-	 * check if a file can be created in $path
222
-	 *
223
-	 * @param string $path
224
-	 * @return bool
225
-	 */
226
-	public function isCreatable($path) {
227
-		return $this->storage->isCreatable($this->findPathToUse($path));
228
-	}
229
-
230
-	/**
231
-	 * check if a file can be read
232
-	 *
233
-	 * @param string $path
234
-	 * @return bool
235
-	 */
236
-	public function isReadable($path) {
237
-		return $this->storage->isReadable($this->findPathToUse($path));
238
-	}
239
-
240
-	/**
241
-	 * check if a file can be written to
242
-	 *
243
-	 * @param string $path
244
-	 * @return bool
245
-	 */
246
-	public function isUpdatable($path) {
247
-		return $this->storage->isUpdatable($this->findPathToUse($path));
248
-	}
249
-
250
-	/**
251
-	 * check if a file can be deleted
252
-	 *
253
-	 * @param string $path
254
-	 * @return bool
255
-	 */
256
-	public function isDeletable($path) {
257
-		return $this->storage->isDeletable($this->findPathToUse($path));
258
-	}
259
-
260
-	/**
261
-	 * check if a file can be shared
262
-	 *
263
-	 * @param string $path
264
-	 * @return bool
265
-	 */
266
-	public function isSharable($path) {
267
-		return $this->storage->isSharable($this->findPathToUse($path));
268
-	}
269
-
270
-	/**
271
-	 * get the full permissions of a path.
272
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
273
-	 *
274
-	 * @param string $path
275
-	 * @return int
276
-	 */
277
-	public function getPermissions($path) {
278
-		return $this->storage->getPermissions($this->findPathToUse($path));
279
-	}
280
-
281
-	/**
282
-	 * see https://www.php.net/manual/en/function.file_exists.php
283
-	 *
284
-	 * @param string $path
285
-	 * @return bool
286
-	 */
287
-	public function file_exists($path) {
288
-		return $this->storage->file_exists($this->findPathToUse($path));
289
-	}
290
-
291
-	/**
292
-	 * see https://www.php.net/manual/en/function.filemtime.php
293
-	 *
294
-	 * @param string $path
295
-	 * @return int|bool
296
-	 */
297
-	public function filemtime($path) {
298
-		return $this->storage->filemtime($this->findPathToUse($path));
299
-	}
300
-
301
-	/**
302
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
303
-	 *
304
-	 * @param string $path
305
-	 * @return string|bool
306
-	 */
307
-	public function file_get_contents($path) {
308
-		return $this->storage->file_get_contents($this->findPathToUse($path));
309
-	}
310
-
311
-	/**
312
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
313
-	 *
314
-	 * @param string $path
315
-	 * @param mixed $data
316
-	 * @return int|false
317
-	 */
318
-	public function file_put_contents($path, $data) {
319
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
320
-	}
321
-
322
-	/**
323
-	 * see https://www.php.net/manual/en/function.unlink.php
324
-	 *
325
-	 * @param string $path
326
-	 * @return bool
327
-	 */
328
-	public function unlink($path) {
329
-		$result = $this->storage->unlink($this->findPathToUse($path));
330
-		if ($result) {
331
-			unset($this->namesCache[$path]);
332
-		}
333
-		return $result;
334
-	}
335
-
336
-	/**
337
-	 * see https://www.php.net/manual/en/function.rename.php
338
-	 *
339
-	 * @param string $path1
340
-	 * @param string $path2
341
-	 * @return bool
342
-	 */
343
-	public function rename($path1, $path2) {
344
-		// second name always NFC
345
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
346
-	}
347
-
348
-	/**
349
-	 * see https://www.php.net/manual/en/function.copy.php
350
-	 *
351
-	 * @param string $path1
352
-	 * @param string $path2
353
-	 * @return bool
354
-	 */
355
-	public function copy($path1, $path2) {
356
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
357
-	}
358
-
359
-	/**
360
-	 * see https://www.php.net/manual/en/function.fopen.php
361
-	 *
362
-	 * @param string $path
363
-	 * @param string $mode
364
-	 * @return resource|bool
365
-	 */
366
-	public function fopen($path, $mode) {
367
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
368
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
369
-			unset($this->namesCache[$path]);
370
-		}
371
-		return $result;
372
-	}
373
-
374
-	/**
375
-	 * get the mimetype for a file or folder
376
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
377
-	 *
378
-	 * @param string $path
379
-	 * @return string|bool
380
-	 */
381
-	public function getMimeType($path) {
382
-		return $this->storage->getMimeType($this->findPathToUse($path));
383
-	}
384
-
385
-	/**
386
-	 * see https://www.php.net/manual/en/function.hash.php
387
-	 *
388
-	 * @param string $type
389
-	 * @param string $path
390
-	 * @param bool $raw
391
-	 * @return string|bool
392
-	 */
393
-	public function hash($type, $path, $raw = false) {
394
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
395
-	}
396
-
397
-	/**
398
-	 * see https://www.php.net/manual/en/function.free_space.php
399
-	 *
400
-	 * @param string $path
401
-	 * @return int|bool
402
-	 */
403
-	public function free_space($path) {
404
-		return $this->storage->free_space($this->findPathToUse($path));
405
-	}
406
-
407
-	/**
408
-	 * search for occurrences of $query in file names
409
-	 *
410
-	 * @param string $query
411
-	 * @return array|bool
412
-	 */
413
-	public function search($query) {
414
-		return $this->storage->search($query);
415
-	}
416
-
417
-	/**
418
-	 * see https://www.php.net/manual/en/function.touch.php
419
-	 * If the backend does not support the operation, false should be returned
420
-	 *
421
-	 * @param string $path
422
-	 * @param int $mtime
423
-	 * @return bool
424
-	 */
425
-	public function touch($path, $mtime = null) {
426
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
427
-	}
428
-
429
-	/**
430
-	 * get the path to a local version of the file.
431
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
432
-	 *
433
-	 * @param string $path
434
-	 * @return string|bool
435
-	 */
436
-	public function getLocalFile($path) {
437
-		return $this->storage->getLocalFile($this->findPathToUse($path));
438
-	}
439
-
440
-	/**
441
-	 * check if a file or folder has been updated since $time
442
-	 *
443
-	 * @param string $path
444
-	 * @param int $time
445
-	 * @return bool
446
-	 *
447
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
448
-	 * returning true for other changes in the folder is optional
449
-	 */
450
-	public function hasUpdated($path, $time) {
451
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
452
-	}
453
-
454
-	/**
455
-	 * get a cache instance for the storage
456
-	 *
457
-	 * @param string $path
458
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
459
-	 * @return \OC\Files\Cache\Cache
460
-	 */
461
-	public function getCache($path = '', $storage = null) {
462
-		if (!$storage) {
463
-			$storage = $this;
464
-		}
465
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
466
-	}
467
-
468
-	/**
469
-	 * get a scanner instance for the storage
470
-	 *
471
-	 * @param string $path
472
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
473
-	 * @return \OC\Files\Cache\Scanner
474
-	 */
475
-	public function getScanner($path = '', $storage = null) {
476
-		if (!$storage) {
477
-			$storage = $this;
478
-		}
479
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
480
-	}
481
-
482
-	/**
483
-	 * get the ETag for a file or folder
484
-	 *
485
-	 * @param string $path
486
-	 * @return string|bool
487
-	 */
488
-	public function getETag($path) {
489
-		return $this->storage->getETag($this->findPathToUse($path));
490
-	}
491
-
492
-	/**
493
-	 * @param IStorage $sourceStorage
494
-	 * @param string $sourceInternalPath
495
-	 * @param string $targetInternalPath
496
-	 * @return bool
497
-	 */
498
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
499
-		if ($sourceStorage === $this) {
500
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
-		}
502
-
503
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
504
-		if ($result) {
505
-			unset($this->namesCache[$targetInternalPath]);
506
-		}
507
-		return $result;
508
-	}
509
-
510
-	/**
511
-	 * @param IStorage $sourceStorage
512
-	 * @param string $sourceInternalPath
513
-	 * @param string $targetInternalPath
514
-	 * @return bool
515
-	 */
516
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
517
-		if ($sourceStorage === $this) {
518
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
519
-			if ($result) {
520
-				unset($this->namesCache[$sourceInternalPath]);
521
-				unset($this->namesCache[$targetInternalPath]);
522
-			}
523
-			return $result;
524
-		}
525
-
526
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
527
-		if ($result) {
528
-			unset($this->namesCache[$sourceInternalPath]);
529
-			unset($this->namesCache[$targetInternalPath]);
530
-		}
531
-		return $result;
532
-	}
533
-
534
-	public function getMetaData($path) {
535
-		return $this->storage->getMetaData($this->findPathToUse($path));
536
-	}
537
-
538
-	public function getDirectoryContent($directory): \Traversable {
539
-		return $this->storage->getDirectoryContent($this->findPathToUse($directory));
540
-	}
43
+    /**
44
+     * @var ICache
45
+     */
46
+    private $namesCache;
47
+
48
+    /**
49
+     * @param array $parameters
50
+     */
51
+    public function __construct($parameters) {
52
+        $this->storage = $parameters['storage'];
53
+        $this->namesCache = new CappedMemoryCache();
54
+    }
55
+
56
+    /**
57
+     * Returns whether the given string is only made of ASCII characters
58
+     *
59
+     * @param string $str string
60
+     *
61
+     * @return bool true if the string is all ASCII, false otherwise
62
+     */
63
+    private function isAscii($str) {
64
+        return !preg_match('/[\\x80-\\xff]+/', $str);
65
+    }
66
+
67
+    /**
68
+     * Checks whether the given path exists in NFC or NFD form after checking
69
+     * each form for each path section and returns the correct form.
70
+     * If no existing path found, returns the path as it was given.
71
+     *
72
+     * @param string $fullPath path to check
73
+     *
74
+     * @return string original or converted path
75
+     */
76
+    private function findPathToUse($fullPath) {
77
+        $cachedPath = $this->namesCache[$fullPath];
78
+        if ($cachedPath !== null) {
79
+            return $cachedPath;
80
+        }
81
+
82
+        $sections = explode('/', $fullPath);
83
+        $path = '';
84
+        foreach ($sections as $section) {
85
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
86
+            if ($convertedPath === null) {
87
+                // no point in continuing if the section was not found, use original path
88
+                return $fullPath;
89
+            }
90
+            $path = $convertedPath . '/';
91
+        }
92
+        $path = rtrim($path, '/');
93
+        return $path;
94
+    }
95
+
96
+    /**
97
+     * Checks whether the last path section of the given path exists in NFC or NFD form
98
+     * and returns the correct form. If no existing path found, returns null.
99
+     *
100
+     * @param string $basePath base path to check
101
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
102
+     *
103
+     * @return string|null original or converted path, or null if none of the forms was found
104
+     */
105
+    private function findPathToUseLastSection($basePath, $lastSection) {
106
+        $fullPath = $basePath . $lastSection;
107
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
108
+            $this->namesCache[$fullPath] = $fullPath;
109
+            return $fullPath;
110
+        }
111
+
112
+        // swap encoding
113
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
114
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
115
+        } else {
116
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
117
+        }
118
+        $otherFullPath = $basePath . $otherFormPath;
119
+        if ($this->storage->file_exists($otherFullPath)) {
120
+            $this->namesCache[$fullPath] = $otherFullPath;
121
+            return $otherFullPath;
122
+        }
123
+
124
+        // return original path, file did not exist at all
125
+        $this->namesCache[$fullPath] = $fullPath;
126
+        return null;
127
+    }
128
+
129
+    /**
130
+     * see https://www.php.net/manual/en/function.mkdir.php
131
+     *
132
+     * @param string $path
133
+     * @return bool
134
+     */
135
+    public function mkdir($path) {
136
+        // note: no conversion here, method should not be called with non-NFC names!
137
+        $result = $this->storage->mkdir($path);
138
+        if ($result) {
139
+            $this->namesCache[$path] = $path;
140
+        }
141
+        return $result;
142
+    }
143
+
144
+    /**
145
+     * see https://www.php.net/manual/en/function.rmdir.php
146
+     *
147
+     * @param string $path
148
+     * @return bool
149
+     */
150
+    public function rmdir($path) {
151
+        $result = $this->storage->rmdir($this->findPathToUse($path));
152
+        if ($result) {
153
+            unset($this->namesCache[$path]);
154
+        }
155
+        return $result;
156
+    }
157
+
158
+    /**
159
+     * see https://www.php.net/manual/en/function.opendir.php
160
+     *
161
+     * @param string $path
162
+     * @return resource|bool
163
+     */
164
+    public function opendir($path) {
165
+        return $this->storage->opendir($this->findPathToUse($path));
166
+    }
167
+
168
+    /**
169
+     * see https://www.php.net/manual/en/function.is_dir.php
170
+     *
171
+     * @param string $path
172
+     * @return bool
173
+     */
174
+    public function is_dir($path) {
175
+        return $this->storage->is_dir($this->findPathToUse($path));
176
+    }
177
+
178
+    /**
179
+     * see https://www.php.net/manual/en/function.is_file.php
180
+     *
181
+     * @param string $path
182
+     * @return bool
183
+     */
184
+    public function is_file($path) {
185
+        return $this->storage->is_file($this->findPathToUse($path));
186
+    }
187
+
188
+    /**
189
+     * see https://www.php.net/manual/en/function.stat.php
190
+     * only the following keys are required in the result: size and mtime
191
+     *
192
+     * @param string $path
193
+     * @return array|bool
194
+     */
195
+    public function stat($path) {
196
+        return $this->storage->stat($this->findPathToUse($path));
197
+    }
198
+
199
+    /**
200
+     * see https://www.php.net/manual/en/function.filetype.php
201
+     *
202
+     * @param string $path
203
+     * @return string|bool
204
+     */
205
+    public function filetype($path) {
206
+        return $this->storage->filetype($this->findPathToUse($path));
207
+    }
208
+
209
+    /**
210
+     * see https://www.php.net/manual/en/function.filesize.php
211
+     * The result for filesize when called on a folder is required to be 0
212
+     *
213
+     * @param string $path
214
+     * @return int|bool
215
+     */
216
+    public function filesize($path) {
217
+        return $this->storage->filesize($this->findPathToUse($path));
218
+    }
219
+
220
+    /**
221
+     * check if a file can be created in $path
222
+     *
223
+     * @param string $path
224
+     * @return bool
225
+     */
226
+    public function isCreatable($path) {
227
+        return $this->storage->isCreatable($this->findPathToUse($path));
228
+    }
229
+
230
+    /**
231
+     * check if a file can be read
232
+     *
233
+     * @param string $path
234
+     * @return bool
235
+     */
236
+    public function isReadable($path) {
237
+        return $this->storage->isReadable($this->findPathToUse($path));
238
+    }
239
+
240
+    /**
241
+     * check if a file can be written to
242
+     *
243
+     * @param string $path
244
+     * @return bool
245
+     */
246
+    public function isUpdatable($path) {
247
+        return $this->storage->isUpdatable($this->findPathToUse($path));
248
+    }
249
+
250
+    /**
251
+     * check if a file can be deleted
252
+     *
253
+     * @param string $path
254
+     * @return bool
255
+     */
256
+    public function isDeletable($path) {
257
+        return $this->storage->isDeletable($this->findPathToUse($path));
258
+    }
259
+
260
+    /**
261
+     * check if a file can be shared
262
+     *
263
+     * @param string $path
264
+     * @return bool
265
+     */
266
+    public function isSharable($path) {
267
+        return $this->storage->isSharable($this->findPathToUse($path));
268
+    }
269
+
270
+    /**
271
+     * get the full permissions of a path.
272
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
273
+     *
274
+     * @param string $path
275
+     * @return int
276
+     */
277
+    public function getPermissions($path) {
278
+        return $this->storage->getPermissions($this->findPathToUse($path));
279
+    }
280
+
281
+    /**
282
+     * see https://www.php.net/manual/en/function.file_exists.php
283
+     *
284
+     * @param string $path
285
+     * @return bool
286
+     */
287
+    public function file_exists($path) {
288
+        return $this->storage->file_exists($this->findPathToUse($path));
289
+    }
290
+
291
+    /**
292
+     * see https://www.php.net/manual/en/function.filemtime.php
293
+     *
294
+     * @param string $path
295
+     * @return int|bool
296
+     */
297
+    public function filemtime($path) {
298
+        return $this->storage->filemtime($this->findPathToUse($path));
299
+    }
300
+
301
+    /**
302
+     * see https://www.php.net/manual/en/function.file_get_contents.php
303
+     *
304
+     * @param string $path
305
+     * @return string|bool
306
+     */
307
+    public function file_get_contents($path) {
308
+        return $this->storage->file_get_contents($this->findPathToUse($path));
309
+    }
310
+
311
+    /**
312
+     * see https://www.php.net/manual/en/function.file_put_contents.php
313
+     *
314
+     * @param string $path
315
+     * @param mixed $data
316
+     * @return int|false
317
+     */
318
+    public function file_put_contents($path, $data) {
319
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
320
+    }
321
+
322
+    /**
323
+     * see https://www.php.net/manual/en/function.unlink.php
324
+     *
325
+     * @param string $path
326
+     * @return bool
327
+     */
328
+    public function unlink($path) {
329
+        $result = $this->storage->unlink($this->findPathToUse($path));
330
+        if ($result) {
331
+            unset($this->namesCache[$path]);
332
+        }
333
+        return $result;
334
+    }
335
+
336
+    /**
337
+     * see https://www.php.net/manual/en/function.rename.php
338
+     *
339
+     * @param string $path1
340
+     * @param string $path2
341
+     * @return bool
342
+     */
343
+    public function rename($path1, $path2) {
344
+        // second name always NFC
345
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
346
+    }
347
+
348
+    /**
349
+     * see https://www.php.net/manual/en/function.copy.php
350
+     *
351
+     * @param string $path1
352
+     * @param string $path2
353
+     * @return bool
354
+     */
355
+    public function copy($path1, $path2) {
356
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
357
+    }
358
+
359
+    /**
360
+     * see https://www.php.net/manual/en/function.fopen.php
361
+     *
362
+     * @param string $path
363
+     * @param string $mode
364
+     * @return resource|bool
365
+     */
366
+    public function fopen($path, $mode) {
367
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
368
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
369
+            unset($this->namesCache[$path]);
370
+        }
371
+        return $result;
372
+    }
373
+
374
+    /**
375
+     * get the mimetype for a file or folder
376
+     * The mimetype for a folder is required to be "httpd/unix-directory"
377
+     *
378
+     * @param string $path
379
+     * @return string|bool
380
+     */
381
+    public function getMimeType($path) {
382
+        return $this->storage->getMimeType($this->findPathToUse($path));
383
+    }
384
+
385
+    /**
386
+     * see https://www.php.net/manual/en/function.hash.php
387
+     *
388
+     * @param string $type
389
+     * @param string $path
390
+     * @param bool $raw
391
+     * @return string|bool
392
+     */
393
+    public function hash($type, $path, $raw = false) {
394
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
395
+    }
396
+
397
+    /**
398
+     * see https://www.php.net/manual/en/function.free_space.php
399
+     *
400
+     * @param string $path
401
+     * @return int|bool
402
+     */
403
+    public function free_space($path) {
404
+        return $this->storage->free_space($this->findPathToUse($path));
405
+    }
406
+
407
+    /**
408
+     * search for occurrences of $query in file names
409
+     *
410
+     * @param string $query
411
+     * @return array|bool
412
+     */
413
+    public function search($query) {
414
+        return $this->storage->search($query);
415
+    }
416
+
417
+    /**
418
+     * see https://www.php.net/manual/en/function.touch.php
419
+     * If the backend does not support the operation, false should be returned
420
+     *
421
+     * @param string $path
422
+     * @param int $mtime
423
+     * @return bool
424
+     */
425
+    public function touch($path, $mtime = null) {
426
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
427
+    }
428
+
429
+    /**
430
+     * get the path to a local version of the file.
431
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
432
+     *
433
+     * @param string $path
434
+     * @return string|bool
435
+     */
436
+    public function getLocalFile($path) {
437
+        return $this->storage->getLocalFile($this->findPathToUse($path));
438
+    }
439
+
440
+    /**
441
+     * check if a file or folder has been updated since $time
442
+     *
443
+     * @param string $path
444
+     * @param int $time
445
+     * @return bool
446
+     *
447
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
448
+     * returning true for other changes in the folder is optional
449
+     */
450
+    public function hasUpdated($path, $time) {
451
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
452
+    }
453
+
454
+    /**
455
+     * get a cache instance for the storage
456
+     *
457
+     * @param string $path
458
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
459
+     * @return \OC\Files\Cache\Cache
460
+     */
461
+    public function getCache($path = '', $storage = null) {
462
+        if (!$storage) {
463
+            $storage = $this;
464
+        }
465
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
466
+    }
467
+
468
+    /**
469
+     * get a scanner instance for the storage
470
+     *
471
+     * @param string $path
472
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
473
+     * @return \OC\Files\Cache\Scanner
474
+     */
475
+    public function getScanner($path = '', $storage = null) {
476
+        if (!$storage) {
477
+            $storage = $this;
478
+        }
479
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
480
+    }
481
+
482
+    /**
483
+     * get the ETag for a file or folder
484
+     *
485
+     * @param string $path
486
+     * @return string|bool
487
+     */
488
+    public function getETag($path) {
489
+        return $this->storage->getETag($this->findPathToUse($path));
490
+    }
491
+
492
+    /**
493
+     * @param IStorage $sourceStorage
494
+     * @param string $sourceInternalPath
495
+     * @param string $targetInternalPath
496
+     * @return bool
497
+     */
498
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
499
+        if ($sourceStorage === $this) {
500
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
+        }
502
+
503
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
504
+        if ($result) {
505
+            unset($this->namesCache[$targetInternalPath]);
506
+        }
507
+        return $result;
508
+    }
509
+
510
+    /**
511
+     * @param IStorage $sourceStorage
512
+     * @param string $sourceInternalPath
513
+     * @param string $targetInternalPath
514
+     * @return bool
515
+     */
516
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
517
+        if ($sourceStorage === $this) {
518
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
519
+            if ($result) {
520
+                unset($this->namesCache[$sourceInternalPath]);
521
+                unset($this->namesCache[$targetInternalPath]);
522
+            }
523
+            return $result;
524
+        }
525
+
526
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
527
+        if ($result) {
528
+            unset($this->namesCache[$sourceInternalPath]);
529
+            unset($this->namesCache[$targetInternalPath]);
530
+        }
531
+        return $result;
532
+    }
533
+
534
+    public function getMetaData($path) {
535
+        return $this->storage->getMetaData($this->findPathToUse($path));
536
+    }
537
+
538
+    public function getDirectoryContent($directory): \Traversable {
539
+        return $this->storage->getDirectoryContent($this->findPathToUse($directory));
540
+    }
541 541
 }
Please login to merge, or discard this patch.
lib/public/Files/Cache/IScanner.php 1 patch
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -29,56 +29,56 @@
 block discarded – undo
29 29
  * @since 9.0.0
30 30
  */
31 31
 interface IScanner {
32
-	public const SCAN_RECURSIVE_INCOMPLETE = 2; // only recursive into not fully scanned folders
33
-	public const SCAN_RECURSIVE = true;
34
-	public const SCAN_SHALLOW = false;
32
+    public const SCAN_RECURSIVE_INCOMPLETE = 2; // only recursive into not fully scanned folders
33
+    public const SCAN_RECURSIVE = true;
34
+    public const SCAN_SHALLOW = false;
35 35
 
36
-	public const REUSE_NONE = 0;
37
-	public const REUSE_ETAG = 1;
38
-	public const REUSE_SIZE = 2;
36
+    public const REUSE_NONE = 0;
37
+    public const REUSE_ETAG = 1;
38
+    public const REUSE_SIZE = 2;
39 39
 
40
-	/**
41
-	 * scan a single file and store it in the cache
42
-	 *
43
-	 * @param string $file
44
-	 * @param int $reuseExisting
45
-	 * @param int $parentId
46
-	 * @param array | null $cacheData existing data in the cache for the file to be scanned
47
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
48
-	 * @return array | null an array of metadata of the scanned file
49
-	 * @throws \OC\ServerNotAvailableException
50
-	 * @throws \OCP\Lock\LockedException
51
-	 * @since 9.0.0
52
-	 */
53
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true);
40
+    /**
41
+     * scan a single file and store it in the cache
42
+     *
43
+     * @param string $file
44
+     * @param int $reuseExisting
45
+     * @param int $parentId
46
+     * @param array | null $cacheData existing data in the cache for the file to be scanned
47
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
48
+     * @return array | null an array of metadata of the scanned file
49
+     * @throws \OC\ServerNotAvailableException
50
+     * @throws \OCP\Lock\LockedException
51
+     * @since 9.0.0
52
+     */
53
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true);
54 54
 
55
-	/**
56
-	 * scan a folder and all its children
57
-	 *
58
-	 * @param string $path
59
-	 * @param bool $recursive
60
-	 * @param int $reuse
61
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
62
-	 * @return array | null an array of the meta data of the scanned file or folder
63
-	 * @since 9.0.0
64
-	 */
65
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true);
55
+    /**
56
+     * scan a folder and all its children
57
+     *
58
+     * @param string $path
59
+     * @param bool $recursive
60
+     * @param int $reuse
61
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
62
+     * @return array | null an array of the meta data of the scanned file or folder
63
+     * @since 9.0.0
64
+     */
65
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true);
66 66
 
67
-	/**
68
-	 * check if the file should be ignored when scanning
69
-	 * NOTE: files with a '.part' extension are ignored as well!
70
-	 *       prevents unfinished put requests to be scanned
71
-	 *
72
-	 * @param string $file
73
-	 * @return boolean
74
-	 * @since 9.0.0
75
-	 */
76
-	public static function isPartialFile($file);
67
+    /**
68
+     * check if the file should be ignored when scanning
69
+     * NOTE: files with a '.part' extension are ignored as well!
70
+     *       prevents unfinished put requests to be scanned
71
+     *
72
+     * @param string $file
73
+     * @return boolean
74
+     * @since 9.0.0
75
+     */
76
+    public static function isPartialFile($file);
77 77
 
78
-	/**
79
-	 * walk over any folders that are not fully scanned yet and scan them
80
-	 *
81
-	 * @since 9.0.0
82
-	 */
83
-	public function backgroundScan();
78
+    /**
79
+     * walk over any folders that are not fully scanned yet and scan them
80
+     *
81
+     * @since 9.0.0
82
+     */
83
+    public function backgroundScan();
84 84
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/External/Scanner.php 1 patch
Indentation   +87 added lines, -87 removed lines patch added patch discarded remove patch
@@ -33,97 +33,97 @@
 block discarded – undo
33 33
 use OCP\Files\StorageNotAvailableException;
34 34
 
35 35
 class Scanner extends \OC\Files\Cache\Scanner {
36
-	/** @var \OCA\Files_Sharing\External\Storage */
37
-	protected $storage;
36
+    /** @var \OCA\Files_Sharing\External\Storage */
37
+    protected $storage;
38 38
 
39
-	/** {@inheritDoc} */
40
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
41
-		if (!$this->storage->remoteIsOwnCloud()) {
42
-			return parent::scan($path, $recursive, $recursive, $lock);
43
-		}
39
+    /** {@inheritDoc} */
40
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
41
+        if (!$this->storage->remoteIsOwnCloud()) {
42
+            return parent::scan($path, $recursive, $recursive, $lock);
43
+        }
44 44
 
45
-		$this->scanAll();
46
-	}
45
+        $this->scanAll();
46
+    }
47 47
 
48
-	/**
49
-	 * Scan a single file and store it in the cache.
50
-	 * If an exception happened while accessing the external storage,
51
-	 * the storage will be checked for availability and removed
52
-	 * if it is not available any more.
53
-	 *
54
-	 * @param string $file file to scan
55
-	 * @param int $reuseExisting
56
-	 * @param int $parentId
57
-	 * @param array | null $cacheData existing data in the cache for the file to be scanned
58
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
59
-	 * @return array | null an array of metadata of the scanned file
60
-	 */
61
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
62
-		try {
63
-			return parent::scanFile($file, $reuseExisting);
64
-		} catch (ForbiddenException $e) {
65
-			$this->storage->checkStorageAvailability();
66
-		} catch (NotFoundException $e) {
67
-			// if the storage isn't found, the call to
68
-			// checkStorageAvailable() will verify it and remove it
69
-			// if appropriate
70
-			$this->storage->checkStorageAvailability();
71
-		} catch (StorageInvalidException $e) {
72
-			$this->storage->checkStorageAvailability();
73
-		} catch (StorageNotAvailableException $e) {
74
-			$this->storage->checkStorageAvailability();
75
-		}
76
-	}
48
+    /**
49
+     * Scan a single file and store it in the cache.
50
+     * If an exception happened while accessing the external storage,
51
+     * the storage will be checked for availability and removed
52
+     * if it is not available any more.
53
+     *
54
+     * @param string $file file to scan
55
+     * @param int $reuseExisting
56
+     * @param int $parentId
57
+     * @param array | null $cacheData existing data in the cache for the file to be scanned
58
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
59
+     * @return array | null an array of metadata of the scanned file
60
+     */
61
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
62
+        try {
63
+            return parent::scanFile($file, $reuseExisting);
64
+        } catch (ForbiddenException $e) {
65
+            $this->storage->checkStorageAvailability();
66
+        } catch (NotFoundException $e) {
67
+            // if the storage isn't found, the call to
68
+            // checkStorageAvailable() will verify it and remove it
69
+            // if appropriate
70
+            $this->storage->checkStorageAvailability();
71
+        } catch (StorageInvalidException $e) {
72
+            $this->storage->checkStorageAvailability();
73
+        } catch (StorageNotAvailableException $e) {
74
+            $this->storage->checkStorageAvailability();
75
+        }
76
+    }
77 77
 
78
-	/**
79
-	 * Checks the remote share for changes.
80
-	 * If changes are available, scan them and update
81
-	 * the cache.
82
-	 * @throws NotFoundException
83
-	 * @throws StorageInvalidException
84
-	 * @throws \Exception
85
-	 */
86
-	public function scanAll() {
87
-		try {
88
-			$data = $this->storage->getShareInfo();
89
-		} catch (\Exception $e) {
90
-			$this->storage->checkStorageAvailability();
91
-			throw new \Exception(
92
-				'Error while scanning remote share: "' .
93
-				$this->storage->getRemote() . '" ' .
94
-				$e->getMessage()
95
-			);
96
-		}
97
-		if ($data['status'] === 'success') {
98
-			$this->addResult($data['data'], '');
99
-		} else {
100
-			throw new \Exception(
101
-				'Error while scanning remote share: "' .
102
-				$this->storage->getRemote() . '"'
103
-			);
104
-		}
105
-	}
78
+    /**
79
+     * Checks the remote share for changes.
80
+     * If changes are available, scan them and update
81
+     * the cache.
82
+     * @throws NotFoundException
83
+     * @throws StorageInvalidException
84
+     * @throws \Exception
85
+     */
86
+    public function scanAll() {
87
+        try {
88
+            $data = $this->storage->getShareInfo();
89
+        } catch (\Exception $e) {
90
+            $this->storage->checkStorageAvailability();
91
+            throw new \Exception(
92
+                'Error while scanning remote share: "' .
93
+                $this->storage->getRemote() . '" ' .
94
+                $e->getMessage()
95
+            );
96
+        }
97
+        if ($data['status'] === 'success') {
98
+            $this->addResult($data['data'], '');
99
+        } else {
100
+            throw new \Exception(
101
+                'Error while scanning remote share: "' .
102
+                $this->storage->getRemote() . '"'
103
+            );
104
+        }
105
+    }
106 106
 
107
-	/**
108
-	 * @param array $data
109
-	 * @param string $path
110
-	 */
111
-	private function addResult($data, $path) {
112
-		$id = $this->cache->put($path, $data);
113
-		if (isset($data['children'])) {
114
-			$children = [];
115
-			foreach ($data['children'] as $child) {
116
-				$children[$child['name']] = true;
117
-				$this->addResult($child, ltrim($path . '/' . $child['name'], '/'));
118
-			}
107
+    /**
108
+     * @param array $data
109
+     * @param string $path
110
+     */
111
+    private function addResult($data, $path) {
112
+        $id = $this->cache->put($path, $data);
113
+        if (isset($data['children'])) {
114
+            $children = [];
115
+            foreach ($data['children'] as $child) {
116
+                $children[$child['name']] = true;
117
+                $this->addResult($child, ltrim($path . '/' . $child['name'], '/'));
118
+            }
119 119
 
120
-			$existingCache = $this->cache->getFolderContentsById($id);
121
-			foreach ($existingCache as $existingChild) {
122
-				// if an existing child is not in the new data, remove it
123
-				if (!isset($children[$existingChild['name']])) {
124
-					$this->cache->remove(ltrim($path . '/' . $existingChild['name'], '/'));
125
-				}
126
-			}
127
-		}
128
-	}
120
+            $existingCache = $this->cache->getFolderContentsById($id);
121
+            foreach ($existingCache as $existingChild) {
122
+                // if an existing child is not in the new data, remove it
123
+                if (!isset($children[$existingChild['name']])) {
124
+                    $this->cache->remove(ltrim($path . '/' . $existingChild['name'], '/'));
125
+                }
126
+            }
127
+        }
128
+    }
129 129
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Scanner.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -32,51 +32,51 @@
 block discarded – undo
32 32
  * Scanner for SharedStorage
33 33
  */
34 34
 class Scanner extends \OC\Files\Cache\Scanner {
35
-	/**
36
-	 * @var \OCA\Files_Sharing\SharedStorage $storage
37
-	 */
38
-	protected $storage;
35
+    /**
36
+     * @var \OCA\Files_Sharing\SharedStorage $storage
37
+     */
38
+    protected $storage;
39 39
 
40
-	private $sourceScanner;
40
+    private $sourceScanner;
41 41
 
42
-	/**
43
-	 * Returns metadata from the shared storage, but
44
-	 * with permissions from the source storage.
45
-	 *
46
-	 * @param string $path path of the file for which to retrieve metadata
47
-	 *
48
-	 * @return array|null an array of metadata of the file
49
-	 */
50
-	public function getData($path) {
51
-		$data = parent::getData($path);
52
-		if ($data === null) {
53
-			return null;
54
-		}
55
-		$internalPath = $this->storage->getUnjailedPath($path);
56
-		$data['permissions'] = $this->storage->getSourceStorage()->getPermissions($internalPath);
57
-		return $data;
58
-	}
42
+    /**
43
+     * Returns metadata from the shared storage, but
44
+     * with permissions from the source storage.
45
+     *
46
+     * @param string $path path of the file for which to retrieve metadata
47
+     *
48
+     * @return array|null an array of metadata of the file
49
+     */
50
+    public function getData($path) {
51
+        $data = parent::getData($path);
52
+        if ($data === null) {
53
+            return null;
54
+        }
55
+        $internalPath = $this->storage->getUnjailedPath($path);
56
+        $data['permissions'] = $this->storage->getSourceStorage()->getPermissions($internalPath);
57
+        return $data;
58
+    }
59 59
 
60
-	private function getSourceScanner() {
61
-		if ($this->sourceScanner) {
62
-			return $this->sourceScanner;
63
-		}
64
-		if ($this->storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
65
-			/** @var \OC\Files\Storage\Storage $storage */
66
-			[$storage] = $this->storage->resolvePath('');
67
-			$this->sourceScanner = $storage->getScanner();
68
-			return $this->sourceScanner;
69
-		} else {
70
-			return null;
71
-		}
72
-	}
60
+    private function getSourceScanner() {
61
+        if ($this->sourceScanner) {
62
+            return $this->sourceScanner;
63
+        }
64
+        if ($this->storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
65
+            /** @var \OC\Files\Storage\Storage $storage */
66
+            [$storage] = $this->storage->resolvePath('');
67
+            $this->sourceScanner = $storage->getScanner();
68
+            return $this->sourceScanner;
69
+        } else {
70
+            return null;
71
+        }
72
+    }
73 73
 
74
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
75
-		$sourceScanner = $this->getSourceScanner();
76
-		if ($sourceScanner instanceof NoopScanner) {
77
-			return [];
78
-		} else {
79
-			return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock);
80
-		}
81
-	}
74
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
75
+        $sourceScanner = $this->getSourceScanner();
76
+        if ($sourceScanner instanceof NoopScanner) {
77
+            return [];
78
+        } else {
79
+            return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock);
80
+        }
81
+    }
82 82
 }
Please login to merge, or discard this patch.