Passed
Push — master ( 50d522...35aa34 )
by Christoph
43:03 queued 07:41
created
apps/files_external/lib/Lib/Storage/SMB.php 1 patch
Indentation   +655 added lines, -655 removed lines patch added patch discarded remove patch
@@ -67,659 +67,659 @@
 block discarded – undo
67 67
 use OCP\ILogger;
68 68
 
69 69
 class SMB extends Common implements INotifyStorage {
70
-	/**
71
-	 * @var \Icewind\SMB\IServer
72
-	 */
73
-	protected $server;
74
-
75
-	/**
76
-	 * @var \Icewind\SMB\IShare
77
-	 */
78
-	protected $share;
79
-
80
-	/**
81
-	 * @var string
82
-	 */
83
-	protected $root;
84
-
85
-	/**
86
-	 * @var \Icewind\SMB\IFileInfo[]
87
-	 */
88
-	protected $statCache;
89
-
90
-	/** @var ILogger */
91
-	protected $logger;
92
-
93
-	/** @var bool */
94
-	protected $showHidden;
95
-
96
-	/** @var bool */
97
-	protected $checkAcl;
98
-
99
-	public function __construct($params) {
100
-		if (!isset($params['host'])) {
101
-			throw new \Exception('Invalid configuration, no host provided');
102
-		}
103
-
104
-		if (isset($params['auth'])) {
105
-			$auth = $params['auth'];
106
-		} elseif (isset($params['user']) && isset($params['password']) && isset($params['share'])) {
107
-			[$workgroup, $user] = $this->splitUser($params['user']);
108
-			$auth = new BasicAuth($user, $workgroup, $params['password']);
109
-		} else {
110
-			throw new \Exception('Invalid configuration, no credentials provided');
111
-		}
112
-
113
-		if (isset($params['logger'])) {
114
-			$this->logger = $params['logger'];
115
-		} else {
116
-			$this->logger = \OC::$server->getLogger();
117
-		}
118
-
119
-		$options = new Options();
120
-		if (isset($params['timeout'])) {
121
-			$timeout = (int)$params['timeout'];
122
-			if ($timeout > 0) {
123
-				$options->setTimeout($timeout);
124
-			}
125
-		}
126
-		$serverFactory = new ServerFactory($options);
127
-		$this->server = $serverFactory->createServer($params['host'], $auth);
128
-		$this->share = $this->server->getShare(trim($params['share'], '/'));
129
-
130
-		$this->root = $params['root'] ?? '/';
131
-		$this->root = '/' . ltrim($this->root, '/');
132
-		$this->root = rtrim($this->root, '/') . '/';
133
-
134
-		$this->showHidden = isset($params['show_hidden']) && $params['show_hidden'];
135
-		$this->checkAcl = isset($params['check_acl']) && $params['check_acl'];
136
-
137
-		$this->statCache = new CappedMemoryCache();
138
-		parent::__construct($params);
139
-	}
140
-
141
-	private function splitUser($user) {
142
-		if (strpos($user, '/')) {
143
-			return explode('/', $user, 2);
144
-		} elseif (strpos($user, '\\')) {
145
-			return explode('\\', $user);
146
-		} else {
147
-			return [null, $user];
148
-		}
149
-	}
150
-
151
-	/**
152
-	 * @return string
153
-	 */
154
-	public function getId() {
155
-		// FIXME: double slash to keep compatible with the old storage ids,
156
-		// failure to do so will lead to creation of a new storage id and
157
-		// loss of shares from the storage
158
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
159
-	}
160
-
161
-	/**
162
-	 * @param string $path
163
-	 * @return string
164
-	 */
165
-	protected function buildPath($path) {
166
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
167
-	}
168
-
169
-	protected function relativePath($fullPath) {
170
-		if ($fullPath === $this->root) {
171
-			return '';
172
-		} elseif (substr($fullPath, 0, strlen($this->root)) === $this->root) {
173
-			return substr($fullPath, strlen($this->root));
174
-		} else {
175
-			return null;
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * @param string $path
181
-	 * @return \Icewind\SMB\IFileInfo
182
-	 * @throws StorageAuthException
183
-	 */
184
-	protected function getFileInfo($path) {
185
-		try {
186
-			$path = $this->buildPath($path);
187
-			if (!isset($this->statCache[$path])) {
188
-				$this->statCache[$path] = $this->share->stat($path);
189
-			}
190
-			return $this->statCache[$path];
191
-		} catch (ConnectException $e) {
192
-			$this->throwUnavailable($e);
193
-		} catch (ForbiddenException $e) {
194
-			// with php-smbclient, this exceptions is thrown when the provided password is invalid.
195
-			// Possible is also ForbiddenException with a different error code, so we check it.
196
-			if ($e->getCode() === 1) {
197
-				$this->throwUnavailable($e);
198
-			}
199
-			throw $e;
200
-		}
201
-	}
202
-
203
-	/**
204
-	 * @param \Exception $e
205
-	 * @throws StorageAuthException
206
-	 */
207
-	protected function throwUnavailable(\Exception $e) {
208
-		$this->logger->logException($e, ['message' => 'Error while getting file info']);
209
-		throw new StorageAuthException($e->getMessage(), $e);
210
-	}
211
-
212
-	/**
213
-	 * get the acl from fileinfo that is relevant for the configured user
214
-	 *
215
-	 * @param IFileInfo $file
216
-	 * @return ACL|null
217
-	 */
218
-	private function getACL(IFileInfo $file): ?ACL {
219
-		$acls = $file->getAcls();
220
-		foreach ($acls as $user => $acl) {
221
-			[, $user] = explode('\\', $user); // strip domain
222
-			if ($user === $this->server->getAuth()->getUsername()) {
223
-				return $acl;
224
-			}
225
-		}
226
-
227
-		return null;
228
-	}
229
-
230
-	/**
231
-	 * @param string $path
232
-	 * @return \Icewind\SMB\IFileInfo[]
233
-	 * @throws StorageNotAvailableException
234
-	 */
235
-	protected function getFolderContents($path): iterable {
236
-		try {
237
-			$path = ltrim($this->buildPath($path), '/');
238
-			$files = $this->share->dir($path);
239
-			foreach ($files as $file) {
240
-				$this->statCache[$path . '/' . $file->getName()] = $file;
241
-			}
242
-
243
-			foreach ($files as $file) {
244
-				try {
245
-					// the isHidden check is done before checking the config boolean to ensure that the metadata is always fetch
246
-					// so we trigger the below exceptions where applicable
247
-					$hide = $file->isHidden() && !$this->showHidden;
248
-
249
-					if ($this->checkAcl && $acl = $this->getACL($file)) {
250
-						// if there is no explicit deny, we assume it's allowed
251
-						// this doesn't take inheritance fully into account but if read permissions is denied for a parent we wouldn't be in this folder
252
-						// additionally, it's better to have false negatives here then false positives
253
-						if ($acl->denies(ACL::MASK_READ) || $acl->denies(ACL::MASK_EXECUTE)) {
254
-							$this->logger->debug('Hiding non readable entry ' . $file->getName());
255
-							return false;
256
-						}
257
-					}
258
-
259
-					if ($hide) {
260
-						$this->logger->debug('hiding hidden file ' . $file->getName());
261
-					}
262
-					if (!$hide) {
263
-						yield $file;
264
-					}
265
-				} catch (ForbiddenException $e) {
266
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]);
267
-				} catch (NotFoundException $e) {
268
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]);
269
-				}
270
-			}
271
-		} catch (ConnectException $e) {
272
-			$this->logger->logException($e, ['message' => 'Error while getting folder content']);
273
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
274
-		}
275
-	}
276
-
277
-	/**
278
-	 * @param \Icewind\SMB\IFileInfo $info
279
-	 * @return array
280
-	 */
281
-	protected function formatInfo($info) {
282
-		$result = [
283
-			'size' => $info->getSize(),
284
-			'mtime' => $info->getMTime(),
285
-		];
286
-		if ($info->isDirectory()) {
287
-			$result['type'] = 'dir';
288
-		} else {
289
-			$result['type'] = 'file';
290
-		}
291
-		return $result;
292
-	}
293
-
294
-	/**
295
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
296
-	 *
297
-	 * @param string $source the old name of the path
298
-	 * @param string $target the new name of the path
299
-	 * @return bool true if the rename is successful, false otherwise
300
-	 */
301
-	public function rename($source, $target, $retry = true) {
302
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
303
-			return false;
304
-		}
305
-
306
-		$absoluteSource = $this->buildPath($source);
307
-		$absoluteTarget = $this->buildPath($target);
308
-		try {
309
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
310
-		} catch (AlreadyExistsException $e) {
311
-			if ($retry) {
312
-				$this->remove($target);
313
-				$result = $this->share->rename($absoluteSource, $absoluteTarget, false);
314
-			} else {
315
-				$this->logger->logException($e, ['level' => ILogger::WARN]);
316
-				return false;
317
-			}
318
-		} catch (InvalidArgumentException $e) {
319
-			if ($retry) {
320
-				$this->remove($target);
321
-				$result = $this->share->rename($absoluteSource, $absoluteTarget, false);
322
-			} else {
323
-				$this->logger->logException($e, ['level' => ILogger::WARN]);
324
-				return false;
325
-			}
326
-		} catch (\Exception $e) {
327
-			$this->logger->logException($e, ['level' => ILogger::WARN]);
328
-			return false;
329
-		}
330
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
331
-		return $result;
332
-	}
333
-
334
-	public function stat($path, $retry = true) {
335
-		try {
336
-			$result = $this->formatInfo($this->getFileInfo($path));
337
-		} catch (ForbiddenException $e) {
338
-			return false;
339
-		} catch (NotFoundException $e) {
340
-			return false;
341
-		} catch (TimedOutException $e) {
342
-			if ($retry) {
343
-				return $this->stat($path, false);
344
-			} else {
345
-				throw $e;
346
-			}
347
-		}
348
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
349
-			$result['mtime'] = $this->shareMTime();
350
-		}
351
-		return $result;
352
-	}
353
-
354
-	/**
355
-	 * get the best guess for the modification time of the share
356
-	 *
357
-	 * @return int
358
-	 */
359
-	private function shareMTime() {
360
-		$highestMTime = 0;
361
-		$files = $this->share->dir($this->root);
362
-		foreach ($files as $fileInfo) {
363
-			try {
364
-				if ($fileInfo->getMTime() > $highestMTime) {
365
-					$highestMTime = $fileInfo->getMTime();
366
-				}
367
-			} catch (NotFoundException $e) {
368
-				// Ignore this, can happen on unavailable DFS shares
369
-			} catch (ForbiddenException $e) {
370
-				// Ignore this too - it's a symlink
371
-			}
372
-		}
373
-		return $highestMTime;
374
-	}
375
-
376
-	/**
377
-	 * Check if the path is our root dir (not the smb one)
378
-	 *
379
-	 * @param string $path the path
380
-	 * @return bool
381
-	 */
382
-	private function isRootDir($path) {
383
-		return $path === '' || $path === '/' || $path === '.';
384
-	}
385
-
386
-	/**
387
-	 * Check if our root points to a smb share
388
-	 *
389
-	 * @return bool true if our root points to a share false otherwise
390
-	 */
391
-	private function remoteIsShare() {
392
-		return $this->share->getName() && (!$this->root || $this->root === '/');
393
-	}
394
-
395
-	/**
396
-	 * @param string $path
397
-	 * @return bool
398
-	 */
399
-	public function unlink($path) {
400
-		if ($this->isRootDir($path)) {
401
-			return false;
402
-		}
403
-
404
-		try {
405
-			if ($this->is_dir($path)) {
406
-				return $this->rmdir($path);
407
-			} else {
408
-				$path = $this->buildPath($path);
409
-				unset($this->statCache[$path]);
410
-				$this->share->del($path);
411
-				return true;
412
-			}
413
-		} catch (NotFoundException $e) {
414
-			return false;
415
-		} catch (ForbiddenException $e) {
416
-			return false;
417
-		} catch (ConnectException $e) {
418
-			$this->logger->logException($e, ['message' => 'Error while deleting file']);
419
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
420
-		}
421
-	}
422
-
423
-	/**
424
-	 * check if a file or folder has been updated since $time
425
-	 *
426
-	 * @param string $path
427
-	 * @param int $time
428
-	 * @return bool
429
-	 */
430
-	public function hasUpdated($path, $time) {
431
-		if (!$path and $this->root === '/') {
432
-			// mtime doesn't work for shares, but giving the nature of the backend,
433
-			// doing a full update is still just fast enough
434
-			return true;
435
-		} else {
436
-			$actualTime = $this->filemtime($path);
437
-			return $actualTime > $time;
438
-		}
439
-	}
440
-
441
-	/**
442
-	 * @param string $path
443
-	 * @param string $mode
444
-	 * @return resource|bool
445
-	 */
446
-	public function fopen($path, $mode) {
447
-		$fullPath = $this->buildPath($path);
448
-		try {
449
-			switch ($mode) {
450
-				case 'r':
451
-				case 'rb':
452
-					if (!$this->file_exists($path)) {
453
-						return false;
454
-					}
455
-					return $this->share->read($fullPath);
456
-				case 'w':
457
-				case 'wb':
458
-					$source = $this->share->write($fullPath);
459
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
460
-						unset($this->statCache[$fullPath]);
461
-					});
462
-				case 'a':
463
-				case 'ab':
464
-				case 'r+':
465
-				case 'w+':
466
-				case 'wb+':
467
-				case 'a+':
468
-				case 'x':
469
-				case 'x+':
470
-				case 'c':
471
-				case 'c+':
472
-					//emulate these
473
-					if (strrpos($path, '.') !== false) {
474
-						$ext = substr($path, strrpos($path, '.'));
475
-					} else {
476
-						$ext = '';
477
-					}
478
-					if ($this->file_exists($path)) {
479
-						if (!$this->isUpdatable($path)) {
480
-							return false;
481
-						}
482
-						$tmpFile = $this->getCachedFile($path);
483
-					} else {
484
-						if (!$this->isCreatable(dirname($path))) {
485
-							return false;
486
-						}
487
-						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
488
-					}
489
-					$source = fopen($tmpFile, $mode);
490
-					$share = $this->share;
491
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
492
-						unset($this->statCache[$fullPath]);
493
-						$share->put($tmpFile, $fullPath);
494
-						unlink($tmpFile);
495
-					});
496
-			}
497
-			return false;
498
-		} catch (NotFoundException $e) {
499
-			return false;
500
-		} catch (ForbiddenException $e) {
501
-			return false;
502
-		} catch (OutOfSpaceException $e) {
503
-			throw new EntityTooLargeException("not enough available space to create file", 0, $e);
504
-		} catch (ConnectException $e) {
505
-			$this->logger->logException($e, ['message' => 'Error while opening file']);
506
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
507
-		}
508
-	}
509
-
510
-	public function rmdir($path) {
511
-		if ($this->isRootDir($path)) {
512
-			return false;
513
-		}
514
-
515
-		try {
516
-			$this->statCache = [];
517
-			$content = $this->share->dir($this->buildPath($path));
518
-			foreach ($content as $file) {
519
-				if ($file->isDirectory()) {
520
-					$this->rmdir($path . '/' . $file->getName());
521
-				} else {
522
-					$this->share->del($file->getPath());
523
-				}
524
-			}
525
-			$this->share->rmdir($this->buildPath($path));
526
-			return true;
527
-		} catch (NotFoundException $e) {
528
-			return false;
529
-		} catch (ForbiddenException $e) {
530
-			return false;
531
-		} catch (ConnectException $e) {
532
-			$this->logger->logException($e, ['message' => 'Error while removing folder']);
533
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
534
-		}
535
-	}
536
-
537
-	public function touch($path, $mtime = null) {
538
-		try {
539
-			if (!$this->file_exists($path)) {
540
-				$fh = $this->share->write($this->buildPath($path));
541
-				fclose($fh);
542
-				return true;
543
-			}
544
-			return false;
545
-		} catch (OutOfSpaceException $e) {
546
-			throw new EntityTooLargeException("not enough available space to create file", 0, $e);
547
-		} catch (ConnectException $e) {
548
-			$this->logger->logException($e, ['message' => 'Error while creating file']);
549
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
550
-		}
551
-	}
552
-
553
-	public function getMetaData($path) {
554
-		$fileInfo = $this->getFileInfo($path);
555
-		if (!$fileInfo) {
556
-			return null;
557
-		}
558
-
559
-		return $this->getMetaDataFromFileInfo($fileInfo);
560
-	}
561
-
562
-	private function getMetaDataFromFileInfo(IFileInfo $fileInfo) {
563
-		$permissions = Constants::PERMISSION_READ + Constants::PERMISSION_SHARE;
564
-
565
-		if (!$fileInfo->isReadOnly()) {
566
-			$permissions += Constants::PERMISSION_DELETE;
567
-			$permissions += Constants::PERMISSION_UPDATE;
568
-			if ($fileInfo->isDirectory()) {
569
-				$permissions += Constants::PERMISSION_CREATE;
570
-			}
571
-		}
572
-
573
-		$data = [];
574
-		if ($fileInfo->isDirectory()) {
575
-			$data['mimetype'] = 'httpd/unix-directory';
576
-		} else {
577
-			$data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath());
578
-		}
579
-		$data['mtime'] = $fileInfo->getMTime();
580
-		if ($fileInfo->isDirectory()) {
581
-			$data['size'] = -1; //unknown
582
-		} else {
583
-			$data['size'] = $fileInfo->getSize();
584
-		}
585
-		$data['etag'] = $this->getETag($fileInfo->getPath());
586
-		$data['storage_mtime'] = $data['mtime'];
587
-		$data['permissions'] = $permissions;
588
-		$data['name'] = $fileInfo->getName();
589
-
590
-		return $data;
591
-	}
592
-
593
-	public function opendir($path) {
594
-		try {
595
-			$files = $this->getFolderContents($path);
596
-		} catch (NotFoundException $e) {
597
-			return false;
598
-		} catch (ForbiddenException $e) {
599
-			return false;
600
-		}
601
-		$names = array_map(function ($info) {
602
-			/** @var \Icewind\SMB\IFileInfo $info */
603
-			return $info->getName();
604
-		}, iterator_to_array($files));
605
-		return IteratorDirectory::wrap($names);
606
-	}
607
-
608
-	public function getDirectoryContent($directory): \Traversable {
609
-		$files = $this->getFolderContents($directory);
610
-		foreach ($files as $file) {
611
-			yield $this->getMetaDataFromFileInfo($file);
612
-		}
613
-	}
614
-
615
-	public function filetype($path) {
616
-		try {
617
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
618
-		} catch (NotFoundException $e) {
619
-			return false;
620
-		} catch (ForbiddenException $e) {
621
-			return false;
622
-		}
623
-	}
624
-
625
-	public function mkdir($path) {
626
-		$path = $this->buildPath($path);
627
-		try {
628
-			$this->share->mkdir($path);
629
-			return true;
630
-		} catch (ConnectException $e) {
631
-			$this->logger->logException($e, ['message' => 'Error while creating folder']);
632
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
633
-		} catch (Exception $e) {
634
-			return false;
635
-		}
636
-	}
637
-
638
-	public function file_exists($path) {
639
-		try {
640
-			$this->getFileInfo($path);
641
-			return true;
642
-		} catch (NotFoundException $e) {
643
-			return false;
644
-		} catch (ForbiddenException $e) {
645
-			return false;
646
-		} catch (ConnectException $e) {
647
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
648
-		}
649
-	}
650
-
651
-	public function isReadable($path) {
652
-		try {
653
-			$info = $this->getFileInfo($path);
654
-			return $this->showHidden || !$info->isHidden();
655
-		} catch (NotFoundException $e) {
656
-			return false;
657
-		} catch (ForbiddenException $e) {
658
-			return false;
659
-		}
660
-	}
661
-
662
-	public function isUpdatable($path) {
663
-		try {
664
-			$info = $this->getFileInfo($path);
665
-			// following windows behaviour for read-only folders: they can be written into
666
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
667
-			return ($this->showHidden || !$info->isHidden()) && (!$info->isReadOnly() || $this->is_dir($path));
668
-		} catch (NotFoundException $e) {
669
-			return false;
670
-		} catch (ForbiddenException $e) {
671
-			return false;
672
-		}
673
-	}
674
-
675
-	public function isDeletable($path) {
676
-		try {
677
-			$info = $this->getFileInfo($path);
678
-			return ($this->showHidden || !$info->isHidden()) && !$info->isReadOnly();
679
-		} catch (NotFoundException $e) {
680
-			return false;
681
-		} catch (ForbiddenException $e) {
682
-			return false;
683
-		}
684
-	}
685
-
686
-	/**
687
-	 * check if smbclient is installed
688
-	 */
689
-	public static function checkDependencies() {
690
-		return (
691
-			(bool)\OC_Helper::findBinaryPath('smbclient')
692
-			|| NativeServer::available(new System())
693
-		) ? true : ['smbclient'];
694
-	}
695
-
696
-	/**
697
-	 * Test a storage for availability
698
-	 *
699
-	 * @return bool
700
-	 */
701
-	public function test() {
702
-		try {
703
-			return parent::test();
704
-		} catch (Exception $e) {
705
-			$this->logger->logException($e);
706
-			return false;
707
-		}
708
-	}
709
-
710
-	public function listen($path, callable $callback) {
711
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
712
-			if ($change instanceof IRenameChange) {
713
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
714
-			} else {
715
-				return $callback($change->getType(), $change->getPath());
716
-			}
717
-		});
718
-	}
719
-
720
-	public function notify($path) {
721
-		$path = '/' . ltrim($path, '/');
722
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
723
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
724
-	}
70
+    /**
71
+     * @var \Icewind\SMB\IServer
72
+     */
73
+    protected $server;
74
+
75
+    /**
76
+     * @var \Icewind\SMB\IShare
77
+     */
78
+    protected $share;
79
+
80
+    /**
81
+     * @var string
82
+     */
83
+    protected $root;
84
+
85
+    /**
86
+     * @var \Icewind\SMB\IFileInfo[]
87
+     */
88
+    protected $statCache;
89
+
90
+    /** @var ILogger */
91
+    protected $logger;
92
+
93
+    /** @var bool */
94
+    protected $showHidden;
95
+
96
+    /** @var bool */
97
+    protected $checkAcl;
98
+
99
+    public function __construct($params) {
100
+        if (!isset($params['host'])) {
101
+            throw new \Exception('Invalid configuration, no host provided');
102
+        }
103
+
104
+        if (isset($params['auth'])) {
105
+            $auth = $params['auth'];
106
+        } elseif (isset($params['user']) && isset($params['password']) && isset($params['share'])) {
107
+            [$workgroup, $user] = $this->splitUser($params['user']);
108
+            $auth = new BasicAuth($user, $workgroup, $params['password']);
109
+        } else {
110
+            throw new \Exception('Invalid configuration, no credentials provided');
111
+        }
112
+
113
+        if (isset($params['logger'])) {
114
+            $this->logger = $params['logger'];
115
+        } else {
116
+            $this->logger = \OC::$server->getLogger();
117
+        }
118
+
119
+        $options = new Options();
120
+        if (isset($params['timeout'])) {
121
+            $timeout = (int)$params['timeout'];
122
+            if ($timeout > 0) {
123
+                $options->setTimeout($timeout);
124
+            }
125
+        }
126
+        $serverFactory = new ServerFactory($options);
127
+        $this->server = $serverFactory->createServer($params['host'], $auth);
128
+        $this->share = $this->server->getShare(trim($params['share'], '/'));
129
+
130
+        $this->root = $params['root'] ?? '/';
131
+        $this->root = '/' . ltrim($this->root, '/');
132
+        $this->root = rtrim($this->root, '/') . '/';
133
+
134
+        $this->showHidden = isset($params['show_hidden']) && $params['show_hidden'];
135
+        $this->checkAcl = isset($params['check_acl']) && $params['check_acl'];
136
+
137
+        $this->statCache = new CappedMemoryCache();
138
+        parent::__construct($params);
139
+    }
140
+
141
+    private function splitUser($user) {
142
+        if (strpos($user, '/')) {
143
+            return explode('/', $user, 2);
144
+        } elseif (strpos($user, '\\')) {
145
+            return explode('\\', $user);
146
+        } else {
147
+            return [null, $user];
148
+        }
149
+    }
150
+
151
+    /**
152
+     * @return string
153
+     */
154
+    public function getId() {
155
+        // FIXME: double slash to keep compatible with the old storage ids,
156
+        // failure to do so will lead to creation of a new storage id and
157
+        // loss of shares from the storage
158
+        return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
159
+    }
160
+
161
+    /**
162
+     * @param string $path
163
+     * @return string
164
+     */
165
+    protected function buildPath($path) {
166
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
167
+    }
168
+
169
+    protected function relativePath($fullPath) {
170
+        if ($fullPath === $this->root) {
171
+            return '';
172
+        } elseif (substr($fullPath, 0, strlen($this->root)) === $this->root) {
173
+            return substr($fullPath, strlen($this->root));
174
+        } else {
175
+            return null;
176
+        }
177
+    }
178
+
179
+    /**
180
+     * @param string $path
181
+     * @return \Icewind\SMB\IFileInfo
182
+     * @throws StorageAuthException
183
+     */
184
+    protected function getFileInfo($path) {
185
+        try {
186
+            $path = $this->buildPath($path);
187
+            if (!isset($this->statCache[$path])) {
188
+                $this->statCache[$path] = $this->share->stat($path);
189
+            }
190
+            return $this->statCache[$path];
191
+        } catch (ConnectException $e) {
192
+            $this->throwUnavailable($e);
193
+        } catch (ForbiddenException $e) {
194
+            // with php-smbclient, this exceptions is thrown when the provided password is invalid.
195
+            // Possible is also ForbiddenException with a different error code, so we check it.
196
+            if ($e->getCode() === 1) {
197
+                $this->throwUnavailable($e);
198
+            }
199
+            throw $e;
200
+        }
201
+    }
202
+
203
+    /**
204
+     * @param \Exception $e
205
+     * @throws StorageAuthException
206
+     */
207
+    protected function throwUnavailable(\Exception $e) {
208
+        $this->logger->logException($e, ['message' => 'Error while getting file info']);
209
+        throw new StorageAuthException($e->getMessage(), $e);
210
+    }
211
+
212
+    /**
213
+     * get the acl from fileinfo that is relevant for the configured user
214
+     *
215
+     * @param IFileInfo $file
216
+     * @return ACL|null
217
+     */
218
+    private function getACL(IFileInfo $file): ?ACL {
219
+        $acls = $file->getAcls();
220
+        foreach ($acls as $user => $acl) {
221
+            [, $user] = explode('\\', $user); // strip domain
222
+            if ($user === $this->server->getAuth()->getUsername()) {
223
+                return $acl;
224
+            }
225
+        }
226
+
227
+        return null;
228
+    }
229
+
230
+    /**
231
+     * @param string $path
232
+     * @return \Icewind\SMB\IFileInfo[]
233
+     * @throws StorageNotAvailableException
234
+     */
235
+    protected function getFolderContents($path): iterable {
236
+        try {
237
+            $path = ltrim($this->buildPath($path), '/');
238
+            $files = $this->share->dir($path);
239
+            foreach ($files as $file) {
240
+                $this->statCache[$path . '/' . $file->getName()] = $file;
241
+            }
242
+
243
+            foreach ($files as $file) {
244
+                try {
245
+                    // the isHidden check is done before checking the config boolean to ensure that the metadata is always fetch
246
+                    // so we trigger the below exceptions where applicable
247
+                    $hide = $file->isHidden() && !$this->showHidden;
248
+
249
+                    if ($this->checkAcl && $acl = $this->getACL($file)) {
250
+                        // if there is no explicit deny, we assume it's allowed
251
+                        // this doesn't take inheritance fully into account but if read permissions is denied for a parent we wouldn't be in this folder
252
+                        // additionally, it's better to have false negatives here then false positives
253
+                        if ($acl->denies(ACL::MASK_READ) || $acl->denies(ACL::MASK_EXECUTE)) {
254
+                            $this->logger->debug('Hiding non readable entry ' . $file->getName());
255
+                            return false;
256
+                        }
257
+                    }
258
+
259
+                    if ($hide) {
260
+                        $this->logger->debug('hiding hidden file ' . $file->getName());
261
+                    }
262
+                    if (!$hide) {
263
+                        yield $file;
264
+                    }
265
+                } catch (ForbiddenException $e) {
266
+                    $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]);
267
+                } catch (NotFoundException $e) {
268
+                    $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]);
269
+                }
270
+            }
271
+        } catch (ConnectException $e) {
272
+            $this->logger->logException($e, ['message' => 'Error while getting folder content']);
273
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
274
+        }
275
+    }
276
+
277
+    /**
278
+     * @param \Icewind\SMB\IFileInfo $info
279
+     * @return array
280
+     */
281
+    protected function formatInfo($info) {
282
+        $result = [
283
+            'size' => $info->getSize(),
284
+            'mtime' => $info->getMTime(),
285
+        ];
286
+        if ($info->isDirectory()) {
287
+            $result['type'] = 'dir';
288
+        } else {
289
+            $result['type'] = 'file';
290
+        }
291
+        return $result;
292
+    }
293
+
294
+    /**
295
+     * Rename the files. If the source or the target is the root, the rename won't happen.
296
+     *
297
+     * @param string $source the old name of the path
298
+     * @param string $target the new name of the path
299
+     * @return bool true if the rename is successful, false otherwise
300
+     */
301
+    public function rename($source, $target, $retry = true) {
302
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
303
+            return false;
304
+        }
305
+
306
+        $absoluteSource = $this->buildPath($source);
307
+        $absoluteTarget = $this->buildPath($target);
308
+        try {
309
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
310
+        } catch (AlreadyExistsException $e) {
311
+            if ($retry) {
312
+                $this->remove($target);
313
+                $result = $this->share->rename($absoluteSource, $absoluteTarget, false);
314
+            } else {
315
+                $this->logger->logException($e, ['level' => ILogger::WARN]);
316
+                return false;
317
+            }
318
+        } catch (InvalidArgumentException $e) {
319
+            if ($retry) {
320
+                $this->remove($target);
321
+                $result = $this->share->rename($absoluteSource, $absoluteTarget, false);
322
+            } else {
323
+                $this->logger->logException($e, ['level' => ILogger::WARN]);
324
+                return false;
325
+            }
326
+        } catch (\Exception $e) {
327
+            $this->logger->logException($e, ['level' => ILogger::WARN]);
328
+            return false;
329
+        }
330
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
331
+        return $result;
332
+    }
333
+
334
+    public function stat($path, $retry = true) {
335
+        try {
336
+            $result = $this->formatInfo($this->getFileInfo($path));
337
+        } catch (ForbiddenException $e) {
338
+            return false;
339
+        } catch (NotFoundException $e) {
340
+            return false;
341
+        } catch (TimedOutException $e) {
342
+            if ($retry) {
343
+                return $this->stat($path, false);
344
+            } else {
345
+                throw $e;
346
+            }
347
+        }
348
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
349
+            $result['mtime'] = $this->shareMTime();
350
+        }
351
+        return $result;
352
+    }
353
+
354
+    /**
355
+     * get the best guess for the modification time of the share
356
+     *
357
+     * @return int
358
+     */
359
+    private function shareMTime() {
360
+        $highestMTime = 0;
361
+        $files = $this->share->dir($this->root);
362
+        foreach ($files as $fileInfo) {
363
+            try {
364
+                if ($fileInfo->getMTime() > $highestMTime) {
365
+                    $highestMTime = $fileInfo->getMTime();
366
+                }
367
+            } catch (NotFoundException $e) {
368
+                // Ignore this, can happen on unavailable DFS shares
369
+            } catch (ForbiddenException $e) {
370
+                // Ignore this too - it's a symlink
371
+            }
372
+        }
373
+        return $highestMTime;
374
+    }
375
+
376
+    /**
377
+     * Check if the path is our root dir (not the smb one)
378
+     *
379
+     * @param string $path the path
380
+     * @return bool
381
+     */
382
+    private function isRootDir($path) {
383
+        return $path === '' || $path === '/' || $path === '.';
384
+    }
385
+
386
+    /**
387
+     * Check if our root points to a smb share
388
+     *
389
+     * @return bool true if our root points to a share false otherwise
390
+     */
391
+    private function remoteIsShare() {
392
+        return $this->share->getName() && (!$this->root || $this->root === '/');
393
+    }
394
+
395
+    /**
396
+     * @param string $path
397
+     * @return bool
398
+     */
399
+    public function unlink($path) {
400
+        if ($this->isRootDir($path)) {
401
+            return false;
402
+        }
403
+
404
+        try {
405
+            if ($this->is_dir($path)) {
406
+                return $this->rmdir($path);
407
+            } else {
408
+                $path = $this->buildPath($path);
409
+                unset($this->statCache[$path]);
410
+                $this->share->del($path);
411
+                return true;
412
+            }
413
+        } catch (NotFoundException $e) {
414
+            return false;
415
+        } catch (ForbiddenException $e) {
416
+            return false;
417
+        } catch (ConnectException $e) {
418
+            $this->logger->logException($e, ['message' => 'Error while deleting file']);
419
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
420
+        }
421
+    }
422
+
423
+    /**
424
+     * check if a file or folder has been updated since $time
425
+     *
426
+     * @param string $path
427
+     * @param int $time
428
+     * @return bool
429
+     */
430
+    public function hasUpdated($path, $time) {
431
+        if (!$path and $this->root === '/') {
432
+            // mtime doesn't work for shares, but giving the nature of the backend,
433
+            // doing a full update is still just fast enough
434
+            return true;
435
+        } else {
436
+            $actualTime = $this->filemtime($path);
437
+            return $actualTime > $time;
438
+        }
439
+    }
440
+
441
+    /**
442
+     * @param string $path
443
+     * @param string $mode
444
+     * @return resource|bool
445
+     */
446
+    public function fopen($path, $mode) {
447
+        $fullPath = $this->buildPath($path);
448
+        try {
449
+            switch ($mode) {
450
+                case 'r':
451
+                case 'rb':
452
+                    if (!$this->file_exists($path)) {
453
+                        return false;
454
+                    }
455
+                    return $this->share->read($fullPath);
456
+                case 'w':
457
+                case 'wb':
458
+                    $source = $this->share->write($fullPath);
459
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
460
+                        unset($this->statCache[$fullPath]);
461
+                    });
462
+                case 'a':
463
+                case 'ab':
464
+                case 'r+':
465
+                case 'w+':
466
+                case 'wb+':
467
+                case 'a+':
468
+                case 'x':
469
+                case 'x+':
470
+                case 'c':
471
+                case 'c+':
472
+                    //emulate these
473
+                    if (strrpos($path, '.') !== false) {
474
+                        $ext = substr($path, strrpos($path, '.'));
475
+                    } else {
476
+                        $ext = '';
477
+                    }
478
+                    if ($this->file_exists($path)) {
479
+                        if (!$this->isUpdatable($path)) {
480
+                            return false;
481
+                        }
482
+                        $tmpFile = $this->getCachedFile($path);
483
+                    } else {
484
+                        if (!$this->isCreatable(dirname($path))) {
485
+                            return false;
486
+                        }
487
+                        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
488
+                    }
489
+                    $source = fopen($tmpFile, $mode);
490
+                    $share = $this->share;
491
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
492
+                        unset($this->statCache[$fullPath]);
493
+                        $share->put($tmpFile, $fullPath);
494
+                        unlink($tmpFile);
495
+                    });
496
+            }
497
+            return false;
498
+        } catch (NotFoundException $e) {
499
+            return false;
500
+        } catch (ForbiddenException $e) {
501
+            return false;
502
+        } catch (OutOfSpaceException $e) {
503
+            throw new EntityTooLargeException("not enough available space to create file", 0, $e);
504
+        } catch (ConnectException $e) {
505
+            $this->logger->logException($e, ['message' => 'Error while opening file']);
506
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
507
+        }
508
+    }
509
+
510
+    public function rmdir($path) {
511
+        if ($this->isRootDir($path)) {
512
+            return false;
513
+        }
514
+
515
+        try {
516
+            $this->statCache = [];
517
+            $content = $this->share->dir($this->buildPath($path));
518
+            foreach ($content as $file) {
519
+                if ($file->isDirectory()) {
520
+                    $this->rmdir($path . '/' . $file->getName());
521
+                } else {
522
+                    $this->share->del($file->getPath());
523
+                }
524
+            }
525
+            $this->share->rmdir($this->buildPath($path));
526
+            return true;
527
+        } catch (NotFoundException $e) {
528
+            return false;
529
+        } catch (ForbiddenException $e) {
530
+            return false;
531
+        } catch (ConnectException $e) {
532
+            $this->logger->logException($e, ['message' => 'Error while removing folder']);
533
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
534
+        }
535
+    }
536
+
537
+    public function touch($path, $mtime = null) {
538
+        try {
539
+            if (!$this->file_exists($path)) {
540
+                $fh = $this->share->write($this->buildPath($path));
541
+                fclose($fh);
542
+                return true;
543
+            }
544
+            return false;
545
+        } catch (OutOfSpaceException $e) {
546
+            throw new EntityTooLargeException("not enough available space to create file", 0, $e);
547
+        } catch (ConnectException $e) {
548
+            $this->logger->logException($e, ['message' => 'Error while creating file']);
549
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
550
+        }
551
+    }
552
+
553
+    public function getMetaData($path) {
554
+        $fileInfo = $this->getFileInfo($path);
555
+        if (!$fileInfo) {
556
+            return null;
557
+        }
558
+
559
+        return $this->getMetaDataFromFileInfo($fileInfo);
560
+    }
561
+
562
+    private function getMetaDataFromFileInfo(IFileInfo $fileInfo) {
563
+        $permissions = Constants::PERMISSION_READ + Constants::PERMISSION_SHARE;
564
+
565
+        if (!$fileInfo->isReadOnly()) {
566
+            $permissions += Constants::PERMISSION_DELETE;
567
+            $permissions += Constants::PERMISSION_UPDATE;
568
+            if ($fileInfo->isDirectory()) {
569
+                $permissions += Constants::PERMISSION_CREATE;
570
+            }
571
+        }
572
+
573
+        $data = [];
574
+        if ($fileInfo->isDirectory()) {
575
+            $data['mimetype'] = 'httpd/unix-directory';
576
+        } else {
577
+            $data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath());
578
+        }
579
+        $data['mtime'] = $fileInfo->getMTime();
580
+        if ($fileInfo->isDirectory()) {
581
+            $data['size'] = -1; //unknown
582
+        } else {
583
+            $data['size'] = $fileInfo->getSize();
584
+        }
585
+        $data['etag'] = $this->getETag($fileInfo->getPath());
586
+        $data['storage_mtime'] = $data['mtime'];
587
+        $data['permissions'] = $permissions;
588
+        $data['name'] = $fileInfo->getName();
589
+
590
+        return $data;
591
+    }
592
+
593
+    public function opendir($path) {
594
+        try {
595
+            $files = $this->getFolderContents($path);
596
+        } catch (NotFoundException $e) {
597
+            return false;
598
+        } catch (ForbiddenException $e) {
599
+            return false;
600
+        }
601
+        $names = array_map(function ($info) {
602
+            /** @var \Icewind\SMB\IFileInfo $info */
603
+            return $info->getName();
604
+        }, iterator_to_array($files));
605
+        return IteratorDirectory::wrap($names);
606
+    }
607
+
608
+    public function getDirectoryContent($directory): \Traversable {
609
+        $files = $this->getFolderContents($directory);
610
+        foreach ($files as $file) {
611
+            yield $this->getMetaDataFromFileInfo($file);
612
+        }
613
+    }
614
+
615
+    public function filetype($path) {
616
+        try {
617
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
618
+        } catch (NotFoundException $e) {
619
+            return false;
620
+        } catch (ForbiddenException $e) {
621
+            return false;
622
+        }
623
+    }
624
+
625
+    public function mkdir($path) {
626
+        $path = $this->buildPath($path);
627
+        try {
628
+            $this->share->mkdir($path);
629
+            return true;
630
+        } catch (ConnectException $e) {
631
+            $this->logger->logException($e, ['message' => 'Error while creating folder']);
632
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
633
+        } catch (Exception $e) {
634
+            return false;
635
+        }
636
+    }
637
+
638
+    public function file_exists($path) {
639
+        try {
640
+            $this->getFileInfo($path);
641
+            return true;
642
+        } catch (NotFoundException $e) {
643
+            return false;
644
+        } catch (ForbiddenException $e) {
645
+            return false;
646
+        } catch (ConnectException $e) {
647
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
648
+        }
649
+    }
650
+
651
+    public function isReadable($path) {
652
+        try {
653
+            $info = $this->getFileInfo($path);
654
+            return $this->showHidden || !$info->isHidden();
655
+        } catch (NotFoundException $e) {
656
+            return false;
657
+        } catch (ForbiddenException $e) {
658
+            return false;
659
+        }
660
+    }
661
+
662
+    public function isUpdatable($path) {
663
+        try {
664
+            $info = $this->getFileInfo($path);
665
+            // following windows behaviour for read-only folders: they can be written into
666
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
667
+            return ($this->showHidden || !$info->isHidden()) && (!$info->isReadOnly() || $this->is_dir($path));
668
+        } catch (NotFoundException $e) {
669
+            return false;
670
+        } catch (ForbiddenException $e) {
671
+            return false;
672
+        }
673
+    }
674
+
675
+    public function isDeletable($path) {
676
+        try {
677
+            $info = $this->getFileInfo($path);
678
+            return ($this->showHidden || !$info->isHidden()) && !$info->isReadOnly();
679
+        } catch (NotFoundException $e) {
680
+            return false;
681
+        } catch (ForbiddenException $e) {
682
+            return false;
683
+        }
684
+    }
685
+
686
+    /**
687
+     * check if smbclient is installed
688
+     */
689
+    public static function checkDependencies() {
690
+        return (
691
+            (bool)\OC_Helper::findBinaryPath('smbclient')
692
+            || NativeServer::available(new System())
693
+        ) ? true : ['smbclient'];
694
+    }
695
+
696
+    /**
697
+     * Test a storage for availability
698
+     *
699
+     * @return bool
700
+     */
701
+    public function test() {
702
+        try {
703
+            return parent::test();
704
+        } catch (Exception $e) {
705
+            $this->logger->logException($e);
706
+            return false;
707
+        }
708
+    }
709
+
710
+    public function listen($path, callable $callback) {
711
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
712
+            if ($change instanceof IRenameChange) {
713
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
714
+            } else {
715
+                return $callback($change->getType(), $change->getPath());
716
+            }
717
+        });
718
+    }
719
+
720
+    public function notify($path) {
721
+        $path = '/' . ltrim($path, '/');
722
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
723
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
724
+    }
725 725
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/File.php 1 patch
Indentation   +618 added lines, -618 removed lines patch added patch discarded remove patch
@@ -73,622 +73,622 @@
 block discarded – undo
73 73
 use Sabre\DAV\IFile;
74 74
 
75 75
 class File extends Node implements IFile {
76
-	protected $request;
77
-
78
-	/**
79
-	 * Sets up the node, expects a full path name
80
-	 *
81
-	 * @param \OC\Files\View $view
82
-	 * @param \OCP\Files\FileInfo $info
83
-	 * @param \OCP\Share\IManager $shareManager
84
-	 * @param \OC\AppFramework\Http\Request $request
85
-	 */
86
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
87
-		parent::__construct($view, $info, $shareManager);
88
-
89
-		if (isset($request)) {
90
-			$this->request = $request;
91
-		} else {
92
-			$this->request = \OC::$server->getRequest();
93
-		}
94
-	}
95
-
96
-	/**
97
-	 * Updates the data
98
-	 *
99
-	 * The data argument is a readable stream resource.
100
-	 *
101
-	 * After a successful put operation, you may choose to return an ETag. The
102
-	 * etag must always be surrounded by double-quotes. These quotes must
103
-	 * appear in the actual string you're returning.
104
-	 *
105
-	 * Clients may use the ETag from a PUT request to later on make sure that
106
-	 * when they update the file, the contents haven't changed in the mean
107
-	 * time.
108
-	 *
109
-	 * If you don't plan to store the file byte-by-byte, and you return a
110
-	 * different object on a subsequent GET you are strongly recommended to not
111
-	 * return an ETag, and just return null.
112
-	 *
113
-	 * @param resource $data
114
-	 *
115
-	 * @throws Forbidden
116
-	 * @throws UnsupportedMediaType
117
-	 * @throws BadRequest
118
-	 * @throws Exception
119
-	 * @throws EntityTooLarge
120
-	 * @throws ServiceUnavailable
121
-	 * @throws FileLocked
122
-	 * @return string|null
123
-	 */
124
-	public function put($data) {
125
-		try {
126
-			$exists = $this->fileView->file_exists($this->path);
127
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
128
-				throw new Forbidden();
129
-			}
130
-		} catch (StorageNotAvailableException $e) {
131
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
132
-		}
133
-
134
-		// verify path of the target
135
-		$this->verifyPath();
136
-
137
-		// chunked handling
138
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
139
-			try {
140
-				return $this->createFileChunked($data);
141
-			} catch (\Exception $e) {
142
-				$this->convertToSabreException($e);
143
-			}
144
-		}
145
-
146
-		/** @var Storage $partStorage */
147
-		list($partStorage) = $this->fileView->resolvePath($this->path);
148
-		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
149
-
150
-		$view = \OC\Files\Filesystem::getView();
151
-
152
-		if ($needsPartFile) {
153
-			// mark file as partial while uploading (ignored by the scanner)
154
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
155
-
156
-			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
157
-				$needsPartFile = false;
158
-			}
159
-		}
160
-		if (!$needsPartFile) {
161
-			// upload file directly as the final path
162
-			$partFilePath = $this->path;
163
-
164
-			if ($view && !$this->emitPreHooks($exists)) {
165
-				throw new Exception('Could not write to final file, canceled by hook');
166
-			}
167
-		}
168
-
169
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
170
-		/** @var \OC\Files\Storage\Storage $partStorage */
171
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
172
-		/** @var \OC\Files\Storage\Storage $storage */
173
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
174
-		try {
175
-			if (!$needsPartFile) {
176
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
177
-			}
178
-
179
-			if (!is_resource($data)) {
180
-				$tmpData = fopen('php://temp', 'r+');
181
-				if ($data !== null) {
182
-					fwrite($tmpData, $data);
183
-					rewind($tmpData);
184
-				}
185
-				$data = $tmpData;
186
-			}
187
-
188
-			$data = HashWrapper::wrap($data, 'md5', function ($hash) {
189
-				$this->header('X-Hash-MD5: ' . $hash);
190
-			});
191
-			$data = HashWrapper::wrap($data, 'sha1', function ($hash) {
192
-				$this->header('X-Hash-SHA1: ' . $hash);
193
-			});
194
-			$data = HashWrapper::wrap($data, 'sha256', function ($hash) {
195
-				$this->header('X-Hash-SHA256: ' . $hash);
196
-			});
197
-
198
-			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
199
-				$isEOF = false;
200
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
201
-					$isEOF = feof($stream);
202
-				});
203
-
204
-				$result = true;
205
-				$count = -1;
206
-				try {
207
-					$count = $partStorage->writeStream($internalPartPath, $wrappedData);
208
-				} catch (GenericFileException $e) {
209
-					$result = false;
210
-				}
211
-
212
-
213
-				if ($result === false) {
214
-					$result = $isEOF;
215
-					if (is_resource($wrappedData)) {
216
-						$result = feof($wrappedData);
217
-					}
218
-				}
219
-			} else {
220
-				$target = $partStorage->fopen($internalPartPath, 'wb');
221
-				if ($target === false) {
222
-					\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
223
-					// because we have no clue about the cause we can only throw back a 500/Internal Server Error
224
-					throw new Exception('Could not write file contents');
225
-				}
226
-				list($count, $result) = \OC_Helper::streamCopy($data, $target);
227
-				fclose($target);
228
-			}
229
-
230
-			if ($result === false) {
231
-				$expected = -1;
232
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
233
-					$expected = $_SERVER['CONTENT_LENGTH'];
234
-				}
235
-				if ($expected !== "0") {
236
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
237
-				}
238
-			}
239
-
240
-			// if content length is sent by client:
241
-			// double check if the file was fully received
242
-			// compare expected and actual size
243
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
244
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
245
-				if ($count !== $expected) {
246
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
247
-				}
248
-			}
249
-		} catch (\Exception $e) {
250
-			$context = [];
251
-
252
-			if ($e instanceof LockedException) {
253
-				$context['level'] = ILogger::DEBUG;
254
-			}
255
-
256
-			\OC::$server->getLogger()->logException($e, $context);
257
-			if ($needsPartFile) {
258
-				$partStorage->unlink($internalPartPath);
259
-			}
260
-			$this->convertToSabreException($e);
261
-		}
262
-
263
-		try {
264
-			if ($needsPartFile) {
265
-				if ($view && !$this->emitPreHooks($exists)) {
266
-					$partStorage->unlink($internalPartPath);
267
-					throw new Exception('Could not rename part file to final file, canceled by hook');
268
-				}
269
-				try {
270
-					$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
271
-				} catch (LockedException $e) {
272
-					// during very large uploads, the shared lock we got at the start might have been expired
273
-					// meaning that the above lock can fail not just only because somebody else got a shared lock
274
-					// or because there is no existing shared lock to make exclusive
275
-					//
276
-					// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
277
-					// lock this will still fail, if our original shared lock expired the new lock will be successful and
278
-					// the entire operation will be safe
279
-
280
-					try {
281
-						$this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
282
-					} catch (LockedException $ex) {
283
-						if ($needsPartFile) {
284
-							$partStorage->unlink($internalPartPath);
285
-						}
286
-						throw new FileLocked($e->getMessage(), $e->getCode(), $e);
287
-					}
288
-				}
289
-
290
-				// rename to correct path
291
-				try {
292
-					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
293
-					$fileExists = $storage->file_exists($internalPath);
294
-					if ($renameOkay === false || $fileExists === false) {
295
-						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
296
-						throw new Exception('Could not rename part file to final file');
297
-					}
298
-				} catch (ForbiddenException $ex) {
299
-					if (!$ex->getRetry()) {
300
-						$partStorage->unlink($internalPartPath);
301
-					}
302
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
303
-				} catch (\Exception $e) {
304
-					$partStorage->unlink($internalPartPath);
305
-					$this->convertToSabreException($e);
306
-				}
307
-			}
308
-
309
-			// since we skipped the view we need to scan and emit the hooks ourselves
310
-			$storage->getUpdater()->update($internalPath);
311
-
312
-			try {
313
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
314
-			} catch (LockedException $e) {
315
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
316
-			}
317
-
318
-			// allow sync clients to send the mtime along in a header
319
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
320
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
321
-				if ($this->fileView->touch($this->path, $mtime)) {
322
-					$this->header('X-OC-MTime: accepted');
323
-				}
324
-			}
325
-
326
-			$fileInfoUpdate = [
327
-				'upload_time' => time()
328
-			];
329
-
330
-			// allow sync clients to send the creation time along in a header
331
-			if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
332
-				$ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
333
-				$fileInfoUpdate['creation_time'] = $ctime;
334
-				$this->header('X-OC-CTime: accepted');
335
-			}
336
-
337
-			$this->fileView->putFileInfo($this->path, $fileInfoUpdate);
338
-
339
-			if ($view) {
340
-				$this->emitPostHooks($exists);
341
-			}
342
-
343
-			$this->refreshInfo();
344
-
345
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
346
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
347
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
348
-				$this->refreshInfo();
349
-			} elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
350
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
351
-				$this->refreshInfo();
352
-			}
353
-		} catch (StorageNotAvailableException $e) {
354
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
355
-		}
356
-
357
-		return '"' . $this->info->getEtag() . '"';
358
-	}
359
-
360
-	private function getPartFileBasePath($path) {
361
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
362
-		if ($partFileInStorage) {
363
-			return $path;
364
-		} else {
365
-			return md5($path); // will place it in the root of the view with a unique name
366
-		}
367
-	}
368
-
369
-	/**
370
-	 * @param string $path
371
-	 */
372
-	private function emitPreHooks($exists, $path = null) {
373
-		if (is_null($path)) {
374
-			$path = $this->path;
375
-		}
376
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
377
-		$run = true;
378
-
379
-		if (!$exists) {
380
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
381
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
382
-				\OC\Files\Filesystem::signal_param_run => &$run,
383
-			]);
384
-		} else {
385
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
386
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
387
-				\OC\Files\Filesystem::signal_param_run => &$run,
388
-			]);
389
-		}
390
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
391
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
392
-			\OC\Files\Filesystem::signal_param_run => &$run,
393
-		]);
394
-		return $run;
395
-	}
396
-
397
-	/**
398
-	 * @param string $path
399
-	 */
400
-	private function emitPostHooks($exists, $path = null) {
401
-		if (is_null($path)) {
402
-			$path = $this->path;
403
-		}
404
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
405
-		if (!$exists) {
406
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
407
-				\OC\Files\Filesystem::signal_param_path => $hookPath
408
-			]);
409
-		} else {
410
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
411
-				\OC\Files\Filesystem::signal_param_path => $hookPath
412
-			]);
413
-		}
414
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
415
-			\OC\Files\Filesystem::signal_param_path => $hookPath
416
-		]);
417
-	}
418
-
419
-	/**
420
-	 * Returns the data
421
-	 *
422
-	 * @return resource
423
-	 * @throws Forbidden
424
-	 * @throws ServiceUnavailable
425
-	 */
426
-	public function get() {
427
-		//throw exception if encryption is disabled but files are still encrypted
428
-		try {
429
-			if (!$this->info->isReadable()) {
430
-				// do a if the file did not exist
431
-				throw new NotFound();
432
-			}
433
-			try {
434
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
435
-			} catch (\Exception $e) {
436
-				$this->convertToSabreException($e);
437
-			}
438
-			if ($res === false) {
439
-				throw new ServiceUnavailable("Could not open file");
440
-			}
441
-			return $res;
442
-		} catch (GenericEncryptionException $e) {
443
-			// returning 503 will allow retry of the operation at a later point in time
444
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
445
-		} catch (StorageNotAvailableException $e) {
446
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
447
-		} catch (ForbiddenException $ex) {
448
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
449
-		} catch (LockedException $e) {
450
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
451
-		}
452
-	}
453
-
454
-	/**
455
-	 * Delete the current file
456
-	 *
457
-	 * @throws Forbidden
458
-	 * @throws ServiceUnavailable
459
-	 */
460
-	public function delete() {
461
-		if (!$this->info->isDeletable()) {
462
-			throw new Forbidden();
463
-		}
464
-
465
-		try {
466
-			if (!$this->fileView->unlink($this->path)) {
467
-				// assume it wasn't possible to delete due to permissions
468
-				throw new Forbidden();
469
-			}
470
-		} catch (StorageNotAvailableException $e) {
471
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
472
-		} catch (ForbiddenException $ex) {
473
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
474
-		} catch (LockedException $e) {
475
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
476
-		}
477
-	}
478
-
479
-	/**
480
-	 * Returns the mime-type for a file
481
-	 *
482
-	 * If null is returned, we'll assume application/octet-stream
483
-	 *
484
-	 * @return string
485
-	 */
486
-	public function getContentType() {
487
-		$mimeType = $this->info->getMimetype();
488
-
489
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
490
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
491
-			return $mimeType;
492
-		}
493
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
494
-	}
495
-
496
-	/**
497
-	 * @return array|bool
498
-	 */
499
-	public function getDirectDownload() {
500
-		if (\OCP\App::isEnabled('encryption')) {
501
-			return [];
502
-		}
503
-		/** @var \OCP\Files\Storage $storage */
504
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
505
-		if (is_null($storage)) {
506
-			return [];
507
-		}
508
-
509
-		return $storage->getDirectDownload($internalPath);
510
-	}
511
-
512
-	/**
513
-	 * @param resource $data
514
-	 * @return null|string
515
-	 * @throws Exception
516
-	 * @throws BadRequest
517
-	 * @throws NotImplemented
518
-	 * @throws ServiceUnavailable
519
-	 */
520
-	private function createFileChunked($data) {
521
-		list($path, $name) = \Sabre\Uri\split($this->path);
522
-
523
-		$info = \OC_FileChunking::decodeName($name);
524
-		if (empty($info)) {
525
-			throw new NotImplemented('Invalid chunk name');
526
-		}
527
-
528
-		$chunk_handler = new \OC_FileChunking($info);
529
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
530
-
531
-		//detect aborted upload
532
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
533
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
534
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
535
-				if ($bytesWritten !== $expected) {
536
-					$chunk_handler->remove($info['index']);
537
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
538
-				}
539
-			}
540
-		}
541
-
542
-		if ($chunk_handler->isComplete()) {
543
-			/** @var Storage $storage */
544
-			list($storage,) = $this->fileView->resolvePath($path);
545
-			$needsPartFile = $storage->needsPartFile();
546
-			$partFile = null;
547
-
548
-			$targetPath = $path . '/' . $info['name'];
549
-			/** @var \OC\Files\Storage\Storage $targetStorage */
550
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
551
-
552
-			$exists = $this->fileView->file_exists($targetPath);
553
-
554
-			try {
555
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
556
-
557
-				$this->emitPreHooks($exists, $targetPath);
558
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
559
-				/** @var \OC\Files\Storage\Storage $targetStorage */
560
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
561
-
562
-				if ($needsPartFile) {
563
-					// we first assembly the target file as a part file
564
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
565
-					/** @var \OC\Files\Storage\Storage $targetStorage */
566
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
567
-
568
-
569
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
570
-
571
-					// here is the final atomic rename
572
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
573
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
574
-					if ($renameOkay === false || $fileExists === false) {
575
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
576
-						// only delete if an error occurred and the target file was already created
577
-						if ($fileExists) {
578
-							// set to null to avoid double-deletion when handling exception
579
-							// stray part file
580
-							$partFile = null;
581
-							$targetStorage->unlink($targetInternalPath);
582
-						}
583
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
584
-						throw new Exception('Could not rename part file assembled from chunks');
585
-					}
586
-				} else {
587
-					// assemble directly into the final file
588
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
589
-				}
590
-
591
-				// allow sync clients to send the mtime along in a header
592
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
593
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
594
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
595
-						$this->header('X-OC-MTime: accepted');
596
-					}
597
-				}
598
-
599
-				// since we skipped the view we need to scan and emit the hooks ourselves
600
-				$targetStorage->getUpdater()->update($targetInternalPath);
601
-
602
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
603
-
604
-				$this->emitPostHooks($exists, $targetPath);
605
-
606
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
607
-				$info = $this->fileView->getFileInfo($targetPath);
608
-
609
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
610
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
611
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
612
-				} elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
613
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
614
-				}
615
-
616
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
617
-
618
-				return $info->getEtag();
619
-			} catch (\Exception $e) {
620
-				if ($partFile !== null) {
621
-					$targetStorage->unlink($targetInternalPath);
622
-				}
623
-				$this->convertToSabreException($e);
624
-			}
625
-		}
626
-
627
-		return null;
628
-	}
629
-
630
-	/**
631
-	 * Convert the given exception to a SabreException instance
632
-	 *
633
-	 * @param \Exception $e
634
-	 *
635
-	 * @throws \Sabre\DAV\Exception
636
-	 */
637
-	private function convertToSabreException(\Exception $e) {
638
-		if ($e instanceof \Sabre\DAV\Exception) {
639
-			throw $e;
640
-		}
641
-		if ($e instanceof NotPermittedException) {
642
-			// a more general case - due to whatever reason the content could not be written
643
-			throw new Forbidden($e->getMessage(), 0, $e);
644
-		}
645
-		if ($e instanceof ForbiddenException) {
646
-			// the path for the file was forbidden
647
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
648
-		}
649
-		if ($e instanceof EntityTooLargeException) {
650
-			// the file is too big to be stored
651
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
652
-		}
653
-		if ($e instanceof InvalidContentException) {
654
-			// the file content is not permitted
655
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
656
-		}
657
-		if ($e instanceof InvalidPathException) {
658
-			// the path for the file was not valid
659
-			// TODO: find proper http status code for this case
660
-			throw new Forbidden($e->getMessage(), 0, $e);
661
-		}
662
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
663
-			// the file is currently being written to by another process
664
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
665
-		}
666
-		if ($e instanceof GenericEncryptionException) {
667
-			// returning 503 will allow retry of the operation at a later point in time
668
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
669
-		}
670
-		if ($e instanceof StorageNotAvailableException) {
671
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
672
-		}
673
-		if ($e instanceof NotFoundException) {
674
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
675
-		}
676
-
677
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
678
-	}
679
-
680
-	/**
681
-	 * Get the checksum for this file
682
-	 *
683
-	 * @return string
684
-	 */
685
-	public function getChecksum() {
686
-		return $this->info->getChecksum();
687
-	}
688
-
689
-	protected function header($string) {
690
-		if (!\OC::$CLI) {
691
-			\header($string);
692
-		}
693
-	}
76
+    protected $request;
77
+
78
+    /**
79
+     * Sets up the node, expects a full path name
80
+     *
81
+     * @param \OC\Files\View $view
82
+     * @param \OCP\Files\FileInfo $info
83
+     * @param \OCP\Share\IManager $shareManager
84
+     * @param \OC\AppFramework\Http\Request $request
85
+     */
86
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
87
+        parent::__construct($view, $info, $shareManager);
88
+
89
+        if (isset($request)) {
90
+            $this->request = $request;
91
+        } else {
92
+            $this->request = \OC::$server->getRequest();
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Updates the data
98
+     *
99
+     * The data argument is a readable stream resource.
100
+     *
101
+     * After a successful put operation, you may choose to return an ETag. The
102
+     * etag must always be surrounded by double-quotes. These quotes must
103
+     * appear in the actual string you're returning.
104
+     *
105
+     * Clients may use the ETag from a PUT request to later on make sure that
106
+     * when they update the file, the contents haven't changed in the mean
107
+     * time.
108
+     *
109
+     * If you don't plan to store the file byte-by-byte, and you return a
110
+     * different object on a subsequent GET you are strongly recommended to not
111
+     * return an ETag, and just return null.
112
+     *
113
+     * @param resource $data
114
+     *
115
+     * @throws Forbidden
116
+     * @throws UnsupportedMediaType
117
+     * @throws BadRequest
118
+     * @throws Exception
119
+     * @throws EntityTooLarge
120
+     * @throws ServiceUnavailable
121
+     * @throws FileLocked
122
+     * @return string|null
123
+     */
124
+    public function put($data) {
125
+        try {
126
+            $exists = $this->fileView->file_exists($this->path);
127
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
128
+                throw new Forbidden();
129
+            }
130
+        } catch (StorageNotAvailableException $e) {
131
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
132
+        }
133
+
134
+        // verify path of the target
135
+        $this->verifyPath();
136
+
137
+        // chunked handling
138
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
139
+            try {
140
+                return $this->createFileChunked($data);
141
+            } catch (\Exception $e) {
142
+                $this->convertToSabreException($e);
143
+            }
144
+        }
145
+
146
+        /** @var Storage $partStorage */
147
+        list($partStorage) = $this->fileView->resolvePath($this->path);
148
+        $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
149
+
150
+        $view = \OC\Files\Filesystem::getView();
151
+
152
+        if ($needsPartFile) {
153
+            // mark file as partial while uploading (ignored by the scanner)
154
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
155
+
156
+            if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
157
+                $needsPartFile = false;
158
+            }
159
+        }
160
+        if (!$needsPartFile) {
161
+            // upload file directly as the final path
162
+            $partFilePath = $this->path;
163
+
164
+            if ($view && !$this->emitPreHooks($exists)) {
165
+                throw new Exception('Could not write to final file, canceled by hook');
166
+            }
167
+        }
168
+
169
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
170
+        /** @var \OC\Files\Storage\Storage $partStorage */
171
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
172
+        /** @var \OC\Files\Storage\Storage $storage */
173
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
174
+        try {
175
+            if (!$needsPartFile) {
176
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
177
+            }
178
+
179
+            if (!is_resource($data)) {
180
+                $tmpData = fopen('php://temp', 'r+');
181
+                if ($data !== null) {
182
+                    fwrite($tmpData, $data);
183
+                    rewind($tmpData);
184
+                }
185
+                $data = $tmpData;
186
+            }
187
+
188
+            $data = HashWrapper::wrap($data, 'md5', function ($hash) {
189
+                $this->header('X-Hash-MD5: ' . $hash);
190
+            });
191
+            $data = HashWrapper::wrap($data, 'sha1', function ($hash) {
192
+                $this->header('X-Hash-SHA1: ' . $hash);
193
+            });
194
+            $data = HashWrapper::wrap($data, 'sha256', function ($hash) {
195
+                $this->header('X-Hash-SHA256: ' . $hash);
196
+            });
197
+
198
+            if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
199
+                $isEOF = false;
200
+                $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
201
+                    $isEOF = feof($stream);
202
+                });
203
+
204
+                $result = true;
205
+                $count = -1;
206
+                try {
207
+                    $count = $partStorage->writeStream($internalPartPath, $wrappedData);
208
+                } catch (GenericFileException $e) {
209
+                    $result = false;
210
+                }
211
+
212
+
213
+                if ($result === false) {
214
+                    $result = $isEOF;
215
+                    if (is_resource($wrappedData)) {
216
+                        $result = feof($wrappedData);
217
+                    }
218
+                }
219
+            } else {
220
+                $target = $partStorage->fopen($internalPartPath, 'wb');
221
+                if ($target === false) {
222
+                    \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
223
+                    // because we have no clue about the cause we can only throw back a 500/Internal Server Error
224
+                    throw new Exception('Could not write file contents');
225
+                }
226
+                list($count, $result) = \OC_Helper::streamCopy($data, $target);
227
+                fclose($target);
228
+            }
229
+
230
+            if ($result === false) {
231
+                $expected = -1;
232
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
233
+                    $expected = $_SERVER['CONTENT_LENGTH'];
234
+                }
235
+                if ($expected !== "0") {
236
+                    throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
237
+                }
238
+            }
239
+
240
+            // if content length is sent by client:
241
+            // double check if the file was fully received
242
+            // compare expected and actual size
243
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
244
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
245
+                if ($count !== $expected) {
246
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
247
+                }
248
+            }
249
+        } catch (\Exception $e) {
250
+            $context = [];
251
+
252
+            if ($e instanceof LockedException) {
253
+                $context['level'] = ILogger::DEBUG;
254
+            }
255
+
256
+            \OC::$server->getLogger()->logException($e, $context);
257
+            if ($needsPartFile) {
258
+                $partStorage->unlink($internalPartPath);
259
+            }
260
+            $this->convertToSabreException($e);
261
+        }
262
+
263
+        try {
264
+            if ($needsPartFile) {
265
+                if ($view && !$this->emitPreHooks($exists)) {
266
+                    $partStorage->unlink($internalPartPath);
267
+                    throw new Exception('Could not rename part file to final file, canceled by hook');
268
+                }
269
+                try {
270
+                    $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
271
+                } catch (LockedException $e) {
272
+                    // during very large uploads, the shared lock we got at the start might have been expired
273
+                    // meaning that the above lock can fail not just only because somebody else got a shared lock
274
+                    // or because there is no existing shared lock to make exclusive
275
+                    //
276
+                    // Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
277
+                    // lock this will still fail, if our original shared lock expired the new lock will be successful and
278
+                    // the entire operation will be safe
279
+
280
+                    try {
281
+                        $this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
282
+                    } catch (LockedException $ex) {
283
+                        if ($needsPartFile) {
284
+                            $partStorage->unlink($internalPartPath);
285
+                        }
286
+                        throw new FileLocked($e->getMessage(), $e->getCode(), $e);
287
+                    }
288
+                }
289
+
290
+                // rename to correct path
291
+                try {
292
+                    $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
293
+                    $fileExists = $storage->file_exists($internalPath);
294
+                    if ($renameOkay === false || $fileExists === false) {
295
+                        \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
296
+                        throw new Exception('Could not rename part file to final file');
297
+                    }
298
+                } catch (ForbiddenException $ex) {
299
+                    if (!$ex->getRetry()) {
300
+                        $partStorage->unlink($internalPartPath);
301
+                    }
302
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
303
+                } catch (\Exception $e) {
304
+                    $partStorage->unlink($internalPartPath);
305
+                    $this->convertToSabreException($e);
306
+                }
307
+            }
308
+
309
+            // since we skipped the view we need to scan and emit the hooks ourselves
310
+            $storage->getUpdater()->update($internalPath);
311
+
312
+            try {
313
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
314
+            } catch (LockedException $e) {
315
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
316
+            }
317
+
318
+            // allow sync clients to send the mtime along in a header
319
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
320
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
321
+                if ($this->fileView->touch($this->path, $mtime)) {
322
+                    $this->header('X-OC-MTime: accepted');
323
+                }
324
+            }
325
+
326
+            $fileInfoUpdate = [
327
+                'upload_time' => time()
328
+            ];
329
+
330
+            // allow sync clients to send the creation time along in a header
331
+            if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
332
+                $ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
333
+                $fileInfoUpdate['creation_time'] = $ctime;
334
+                $this->header('X-OC-CTime: accepted');
335
+            }
336
+
337
+            $this->fileView->putFileInfo($this->path, $fileInfoUpdate);
338
+
339
+            if ($view) {
340
+                $this->emitPostHooks($exists);
341
+            }
342
+
343
+            $this->refreshInfo();
344
+
345
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
346
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
347
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
348
+                $this->refreshInfo();
349
+            } elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
350
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
351
+                $this->refreshInfo();
352
+            }
353
+        } catch (StorageNotAvailableException $e) {
354
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
355
+        }
356
+
357
+        return '"' . $this->info->getEtag() . '"';
358
+    }
359
+
360
+    private function getPartFileBasePath($path) {
361
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
362
+        if ($partFileInStorage) {
363
+            return $path;
364
+        } else {
365
+            return md5($path); // will place it in the root of the view with a unique name
366
+        }
367
+    }
368
+
369
+    /**
370
+     * @param string $path
371
+     */
372
+    private function emitPreHooks($exists, $path = null) {
373
+        if (is_null($path)) {
374
+            $path = $this->path;
375
+        }
376
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
377
+        $run = true;
378
+
379
+        if (!$exists) {
380
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
381
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
382
+                \OC\Files\Filesystem::signal_param_run => &$run,
383
+            ]);
384
+        } else {
385
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
386
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
387
+                \OC\Files\Filesystem::signal_param_run => &$run,
388
+            ]);
389
+        }
390
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
391
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
392
+            \OC\Files\Filesystem::signal_param_run => &$run,
393
+        ]);
394
+        return $run;
395
+    }
396
+
397
+    /**
398
+     * @param string $path
399
+     */
400
+    private function emitPostHooks($exists, $path = null) {
401
+        if (is_null($path)) {
402
+            $path = $this->path;
403
+        }
404
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
405
+        if (!$exists) {
406
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
407
+                \OC\Files\Filesystem::signal_param_path => $hookPath
408
+            ]);
409
+        } else {
410
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
411
+                \OC\Files\Filesystem::signal_param_path => $hookPath
412
+            ]);
413
+        }
414
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
415
+            \OC\Files\Filesystem::signal_param_path => $hookPath
416
+        ]);
417
+    }
418
+
419
+    /**
420
+     * Returns the data
421
+     *
422
+     * @return resource
423
+     * @throws Forbidden
424
+     * @throws ServiceUnavailable
425
+     */
426
+    public function get() {
427
+        //throw exception if encryption is disabled but files are still encrypted
428
+        try {
429
+            if (!$this->info->isReadable()) {
430
+                // do a if the file did not exist
431
+                throw new NotFound();
432
+            }
433
+            try {
434
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
435
+            } catch (\Exception $e) {
436
+                $this->convertToSabreException($e);
437
+            }
438
+            if ($res === false) {
439
+                throw new ServiceUnavailable("Could not open file");
440
+            }
441
+            return $res;
442
+        } catch (GenericEncryptionException $e) {
443
+            // returning 503 will allow retry of the operation at a later point in time
444
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
445
+        } catch (StorageNotAvailableException $e) {
446
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
447
+        } catch (ForbiddenException $ex) {
448
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
449
+        } catch (LockedException $e) {
450
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
451
+        }
452
+    }
453
+
454
+    /**
455
+     * Delete the current file
456
+     *
457
+     * @throws Forbidden
458
+     * @throws ServiceUnavailable
459
+     */
460
+    public function delete() {
461
+        if (!$this->info->isDeletable()) {
462
+            throw new Forbidden();
463
+        }
464
+
465
+        try {
466
+            if (!$this->fileView->unlink($this->path)) {
467
+                // assume it wasn't possible to delete due to permissions
468
+                throw new Forbidden();
469
+            }
470
+        } catch (StorageNotAvailableException $e) {
471
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
472
+        } catch (ForbiddenException $ex) {
473
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
474
+        } catch (LockedException $e) {
475
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
476
+        }
477
+    }
478
+
479
+    /**
480
+     * Returns the mime-type for a file
481
+     *
482
+     * If null is returned, we'll assume application/octet-stream
483
+     *
484
+     * @return string
485
+     */
486
+    public function getContentType() {
487
+        $mimeType = $this->info->getMimetype();
488
+
489
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
490
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
491
+            return $mimeType;
492
+        }
493
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
494
+    }
495
+
496
+    /**
497
+     * @return array|bool
498
+     */
499
+    public function getDirectDownload() {
500
+        if (\OCP\App::isEnabled('encryption')) {
501
+            return [];
502
+        }
503
+        /** @var \OCP\Files\Storage $storage */
504
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
505
+        if (is_null($storage)) {
506
+            return [];
507
+        }
508
+
509
+        return $storage->getDirectDownload($internalPath);
510
+    }
511
+
512
+    /**
513
+     * @param resource $data
514
+     * @return null|string
515
+     * @throws Exception
516
+     * @throws BadRequest
517
+     * @throws NotImplemented
518
+     * @throws ServiceUnavailable
519
+     */
520
+    private function createFileChunked($data) {
521
+        list($path, $name) = \Sabre\Uri\split($this->path);
522
+
523
+        $info = \OC_FileChunking::decodeName($name);
524
+        if (empty($info)) {
525
+            throw new NotImplemented('Invalid chunk name');
526
+        }
527
+
528
+        $chunk_handler = new \OC_FileChunking($info);
529
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
530
+
531
+        //detect aborted upload
532
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
533
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
534
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
535
+                if ($bytesWritten !== $expected) {
536
+                    $chunk_handler->remove($info['index']);
537
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
538
+                }
539
+            }
540
+        }
541
+
542
+        if ($chunk_handler->isComplete()) {
543
+            /** @var Storage $storage */
544
+            list($storage,) = $this->fileView->resolvePath($path);
545
+            $needsPartFile = $storage->needsPartFile();
546
+            $partFile = null;
547
+
548
+            $targetPath = $path . '/' . $info['name'];
549
+            /** @var \OC\Files\Storage\Storage $targetStorage */
550
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
551
+
552
+            $exists = $this->fileView->file_exists($targetPath);
553
+
554
+            try {
555
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
556
+
557
+                $this->emitPreHooks($exists, $targetPath);
558
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
559
+                /** @var \OC\Files\Storage\Storage $targetStorage */
560
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
561
+
562
+                if ($needsPartFile) {
563
+                    // we first assembly the target file as a part file
564
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
565
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
566
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
567
+
568
+
569
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
570
+
571
+                    // here is the final atomic rename
572
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
573
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
574
+                    if ($renameOkay === false || $fileExists === false) {
575
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
576
+                        // only delete if an error occurred and the target file was already created
577
+                        if ($fileExists) {
578
+                            // set to null to avoid double-deletion when handling exception
579
+                            // stray part file
580
+                            $partFile = null;
581
+                            $targetStorage->unlink($targetInternalPath);
582
+                        }
583
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
584
+                        throw new Exception('Could not rename part file assembled from chunks');
585
+                    }
586
+                } else {
587
+                    // assemble directly into the final file
588
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
589
+                }
590
+
591
+                // allow sync clients to send the mtime along in a header
592
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
593
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
594
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
595
+                        $this->header('X-OC-MTime: accepted');
596
+                    }
597
+                }
598
+
599
+                // since we skipped the view we need to scan and emit the hooks ourselves
600
+                $targetStorage->getUpdater()->update($targetInternalPath);
601
+
602
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
603
+
604
+                $this->emitPostHooks($exists, $targetPath);
605
+
606
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
607
+                $info = $this->fileView->getFileInfo($targetPath);
608
+
609
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
610
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
611
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
612
+                } elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
613
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
614
+                }
615
+
616
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
617
+
618
+                return $info->getEtag();
619
+            } catch (\Exception $e) {
620
+                if ($partFile !== null) {
621
+                    $targetStorage->unlink($targetInternalPath);
622
+                }
623
+                $this->convertToSabreException($e);
624
+            }
625
+        }
626
+
627
+        return null;
628
+    }
629
+
630
+    /**
631
+     * Convert the given exception to a SabreException instance
632
+     *
633
+     * @param \Exception $e
634
+     *
635
+     * @throws \Sabre\DAV\Exception
636
+     */
637
+    private function convertToSabreException(\Exception $e) {
638
+        if ($e instanceof \Sabre\DAV\Exception) {
639
+            throw $e;
640
+        }
641
+        if ($e instanceof NotPermittedException) {
642
+            // a more general case - due to whatever reason the content could not be written
643
+            throw new Forbidden($e->getMessage(), 0, $e);
644
+        }
645
+        if ($e instanceof ForbiddenException) {
646
+            // the path for the file was forbidden
647
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
648
+        }
649
+        if ($e instanceof EntityTooLargeException) {
650
+            // the file is too big to be stored
651
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
652
+        }
653
+        if ($e instanceof InvalidContentException) {
654
+            // the file content is not permitted
655
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
656
+        }
657
+        if ($e instanceof InvalidPathException) {
658
+            // the path for the file was not valid
659
+            // TODO: find proper http status code for this case
660
+            throw new Forbidden($e->getMessage(), 0, $e);
661
+        }
662
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
663
+            // the file is currently being written to by another process
664
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
665
+        }
666
+        if ($e instanceof GenericEncryptionException) {
667
+            // returning 503 will allow retry of the operation at a later point in time
668
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
669
+        }
670
+        if ($e instanceof StorageNotAvailableException) {
671
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
672
+        }
673
+        if ($e instanceof NotFoundException) {
674
+            throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
675
+        }
676
+
677
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
678
+    }
679
+
680
+    /**
681
+     * Get the checksum for this file
682
+     *
683
+     * @return string
684
+     */
685
+    public function getChecksum() {
686
+        return $this->info->getChecksum();
687
+    }
688
+
689
+    protected function header($string) {
690
+        if (!\OC::$CLI) {
691
+            \header($string);
692
+        }
693
+    }
694 694
 }
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/StorageObjectStore.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -32,70 +32,70 @@
 block discarded – undo
32 32
  * Object store that wraps a storage backend, mostly for testing purposes
33 33
  */
34 34
 class StorageObjectStore implements IObjectStore {
35
-	/** @var IStorage */
36
-	private $storage;
35
+    /** @var IStorage */
36
+    private $storage;
37 37
 
38
-	/**
39
-	 * @param IStorage $storage
40
-	 */
41
-	public function __construct(IStorage $storage) {
42
-		$this->storage = $storage;
43
-	}
38
+    /**
39
+     * @param IStorage $storage
40
+     */
41
+    public function __construct(IStorage $storage) {
42
+        $this->storage = $storage;
43
+    }
44 44
 
45
-	/**
46
-	 * @return string the container or bucket name where objects are stored
47
-	 * @since 7.0.0
48
-	 */
49
-	public function getStorageId() {
50
-		$this->storage->getId();
51
-	}
45
+    /**
46
+     * @return string the container or bucket name where objects are stored
47
+     * @since 7.0.0
48
+     */
49
+    public function getStorageId() {
50
+        $this->storage->getId();
51
+    }
52 52
 
53
-	/**
54
-	 * @param string $urn the unified resource name used to identify the object
55
-	 * @return resource stream with the read data
56
-	 * @throws \Exception when something goes wrong, message will be logged
57
-	 * @since 7.0.0
58
-	 */
59
-	public function readObject($urn) {
60
-		$handle = $this->storage->fopen($urn, 'r');
61
-		if (is_resource($handle)) {
62
-			return $handle;
63
-		}
53
+    /**
54
+     * @param string $urn the unified resource name used to identify the object
55
+     * @return resource stream with the read data
56
+     * @throws \Exception when something goes wrong, message will be logged
57
+     * @since 7.0.0
58
+     */
59
+    public function readObject($urn) {
60
+        $handle = $this->storage->fopen($urn, 'r');
61
+        if (is_resource($handle)) {
62
+            return $handle;
63
+        }
64 64
 
65
-		throw new \Exception();
66
-	}
65
+        throw new \Exception();
66
+    }
67 67
 
68
-	/**
69
-	 * @param string $urn the unified resource name used to identify the object
70
-	 * @param resource $stream stream with the data to write
71
-	 * @throws \Exception when something goes wrong, message will be logged
72
-	 * @since 7.0.0
73
-	 */
74
-	public function writeObject($urn, $stream) {
75
-		$handle = $this->storage->fopen($urn, 'w');
76
-		if ($handle) {
77
-			stream_copy_to_stream($stream, $handle);
78
-			fclose($handle);
79
-		} else {
80
-			throw new \Exception();
81
-		}
82
-	}
68
+    /**
69
+     * @param string $urn the unified resource name used to identify the object
70
+     * @param resource $stream stream with the data to write
71
+     * @throws \Exception when something goes wrong, message will be logged
72
+     * @since 7.0.0
73
+     */
74
+    public function writeObject($urn, $stream) {
75
+        $handle = $this->storage->fopen($urn, 'w');
76
+        if ($handle) {
77
+            stream_copy_to_stream($stream, $handle);
78
+            fclose($handle);
79
+        } else {
80
+            throw new \Exception();
81
+        }
82
+    }
83 83
 
84
-	/**
85
-	 * @param string $urn the unified resource name used to identify the object
86
-	 * @return void
87
-	 * @throws \Exception when something goes wrong, message will be logged
88
-	 * @since 7.0.0
89
-	 */
90
-	public function deleteObject($urn) {
91
-		$this->storage->unlink($urn);
92
-	}
84
+    /**
85
+     * @param string $urn the unified resource name used to identify the object
86
+     * @return void
87
+     * @throws \Exception when something goes wrong, message will be logged
88
+     * @since 7.0.0
89
+     */
90
+    public function deleteObject($urn) {
91
+        $this->storage->unlink($urn);
92
+    }
93 93
 
94
-	public function objectExists($urn) {
95
-		return $this->storage->file_exists($urn);
96
-	}
94
+    public function objectExists($urn) {
95
+        return $this->storage->file_exists($urn);
96
+    }
97 97
 
98
-	public function copyObject($from, $to) {
99
-		$this->storage->copy($from, $to);
100
-	}
98
+    public function copyObject($from, $to) {
99
+        $this->storage->copy($from, $to);
100
+    }
101 101
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 1 patch
Indentation   +809 added lines, -809 removed lines patch added patch discarded remove patch
@@ -76,817 +76,817 @@
 block discarded – undo
76 76
  * in classes which extend it, e.g. $this->stat() .
77 77
  */
78 78
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
79
-	use LocalTempFileTrait;
80
-
81
-	protected $cache;
82
-	protected $scanner;
83
-	protected $watcher;
84
-	protected $propagator;
85
-	protected $storageCache;
86
-	protected $updater;
87
-
88
-	protected $mountOptions = [];
89
-	protected $owner = null;
90
-
91
-	private $shouldLogLocks = null;
92
-	private $logger;
93
-
94
-	public function __construct($parameters) {
95
-	}
96
-
97
-	/**
98
-	 * Remove a file or folder
99
-	 *
100
-	 * @param string $path
101
-	 * @return bool
102
-	 */
103
-	protected function remove($path) {
104
-		if ($this->is_dir($path)) {
105
-			return $this->rmdir($path);
106
-		} elseif ($this->is_file($path)) {
107
-			return $this->unlink($path);
108
-		} else {
109
-			return false;
110
-		}
111
-	}
112
-
113
-	public function is_dir($path) {
114
-		return $this->filetype($path) === 'dir';
115
-	}
116
-
117
-	public function is_file($path) {
118
-		return $this->filetype($path) === 'file';
119
-	}
120
-
121
-	public function filesize($path) {
122
-		if ($this->is_dir($path)) {
123
-			return 0; //by definition
124
-		} else {
125
-			$stat = $this->stat($path);
126
-			if (isset($stat['size'])) {
127
-				return $stat['size'];
128
-			} else {
129
-				return 0;
130
-			}
131
-		}
132
-	}
133
-
134
-	public function isReadable($path) {
135
-		// at least check whether it exists
136
-		// subclasses might want to implement this more thoroughly
137
-		return $this->file_exists($path);
138
-	}
139
-
140
-	public function isUpdatable($path) {
141
-		// at least check whether it exists
142
-		// subclasses might want to implement this more thoroughly
143
-		// a non-existing file/folder isn't updatable
144
-		return $this->file_exists($path);
145
-	}
146
-
147
-	public function isCreatable($path) {
148
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
149
-			return true;
150
-		}
151
-		return false;
152
-	}
153
-
154
-	public function isDeletable($path) {
155
-		if ($path === '' || $path === '/') {
156
-			return false;
157
-		}
158
-		$parent = dirname($path);
159
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
160
-	}
161
-
162
-	public function isSharable($path) {
163
-		return $this->isReadable($path);
164
-	}
165
-
166
-	public function getPermissions($path) {
167
-		$permissions = 0;
168
-		if ($this->isCreatable($path)) {
169
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
170
-		}
171
-		if ($this->isReadable($path)) {
172
-			$permissions |= \OCP\Constants::PERMISSION_READ;
173
-		}
174
-		if ($this->isUpdatable($path)) {
175
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
176
-		}
177
-		if ($this->isDeletable($path)) {
178
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
179
-		}
180
-		if ($this->isSharable($path)) {
181
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
182
-		}
183
-		return $permissions;
184
-	}
185
-
186
-	public function filemtime($path) {
187
-		$stat = $this->stat($path);
188
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
189
-			return $stat['mtime'];
190
-		} else {
191
-			return 0;
192
-		}
193
-	}
194
-
195
-	public function file_get_contents($path) {
196
-		$handle = $this->fopen($path, "r");
197
-		if (!$handle) {
198
-			return false;
199
-		}
200
-		$data = stream_get_contents($handle);
201
-		fclose($handle);
202
-		return $data;
203
-	}
204
-
205
-	public function file_put_contents($path, $data) {
206
-		$handle = $this->fopen($path, "w");
207
-		$this->removeCachedFile($path);
208
-		$count = fwrite($handle, $data);
209
-		fclose($handle);
210
-		return $count;
211
-	}
212
-
213
-	public function rename($path1, $path2) {
214
-		$this->remove($path2);
215
-
216
-		$this->removeCachedFile($path1);
217
-		return $this->copy($path1, $path2) and $this->remove($path1);
218
-	}
219
-
220
-	public function copy($path1, $path2) {
221
-		if ($this->is_dir($path1)) {
222
-			$this->remove($path2);
223
-			$dir = $this->opendir($path1);
224
-			$this->mkdir($path2);
225
-			while ($file = readdir($dir)) {
226
-				if (!Filesystem::isIgnoredDir($file)) {
227
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
-						return false;
229
-					}
230
-				}
231
-			}
232
-			closedir($dir);
233
-			return true;
234
-		} else {
235
-			$source = $this->fopen($path1, 'r');
236
-			$target = $this->fopen($path2, 'w');
237
-			[, $result] = \OC_Helper::streamCopy($source, $target);
238
-			if (!$result) {
239
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
240
-			}
241
-			$this->removeCachedFile($path2);
242
-			return $result;
243
-		}
244
-	}
245
-
246
-	public function getMimeType($path) {
247
-		if ($this->is_dir($path)) {
248
-			return 'httpd/unix-directory';
249
-		} elseif ($this->file_exists($path)) {
250
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
251
-		} else {
252
-			return false;
253
-		}
254
-	}
255
-
256
-	public function hash($type, $path, $raw = false) {
257
-		$fh = $this->fopen($path, 'rb');
258
-		$ctx = hash_init($type);
259
-		hash_update_stream($ctx, $fh);
260
-		fclose($fh);
261
-		return hash_final($ctx, $raw);
262
-	}
263
-
264
-	public function search($query) {
265
-		return $this->searchInDir($query);
266
-	}
267
-
268
-	public function getLocalFile($path) {
269
-		return $this->getCachedFile($path);
270
-	}
271
-
272
-	/**
273
-	 * @param string $path
274
-	 * @param string $target
275
-	 */
276
-	private function addLocalFolder($path, $target) {
277
-		$dh = $this->opendir($path);
278
-		if (is_resource($dh)) {
279
-			while (($file = readdir($dh)) !== false) {
280
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
281
-					if ($this->is_dir($path . '/' . $file)) {
282
-						mkdir($target . '/' . $file);
283
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
284
-					} else {
285
-						$tmp = $this->toTmpFile($path . '/' . $file);
286
-						rename($tmp, $target . '/' . $file);
287
-					}
288
-				}
289
-			}
290
-		}
291
-	}
292
-
293
-	/**
294
-	 * @param string $query
295
-	 * @param string $dir
296
-	 * @return array
297
-	 */
298
-	protected function searchInDir($query, $dir = '') {
299
-		$files = [];
300
-		$dh = $this->opendir($dir);
301
-		if (is_resource($dh)) {
302
-			while (($item = readdir($dh)) !== false) {
303
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
304
-					continue;
305
-				}
306
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
307
-					$files[] = $dir . '/' . $item;
308
-				}
309
-				if ($this->is_dir($dir . '/' . $item)) {
310
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
311
-				}
312
-			}
313
-		}
314
-		closedir($dh);
315
-		return $files;
316
-	}
317
-
318
-	/**
319
-	 * check if a file or folder has been updated since $time
320
-	 *
321
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
322
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
323
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
324
-	 * ownClouds filesystem.
325
-	 *
326
-	 * @param string $path
327
-	 * @param int $time
328
-	 * @return bool
329
-	 */
330
-	public function hasUpdated($path, $time) {
331
-		return $this->filemtime($path) > $time;
332
-	}
333
-
334
-	public function getCache($path = '', $storage = null) {
335
-		if (!$storage) {
336
-			$storage = $this;
337
-		}
338
-		if (!isset($storage->cache)) {
339
-			$storage->cache = new Cache($storage);
340
-		}
341
-		return $storage->cache;
342
-	}
343
-
344
-	public function getScanner($path = '', $storage = null) {
345
-		if (!$storage) {
346
-			$storage = $this;
347
-		}
348
-		if (!isset($storage->scanner)) {
349
-			$storage->scanner = new Scanner($storage);
350
-		}
351
-		return $storage->scanner;
352
-	}
353
-
354
-	public function getWatcher($path = '', $storage = null) {
355
-		if (!$storage) {
356
-			$storage = $this;
357
-		}
358
-		if (!isset($this->watcher)) {
359
-			$this->watcher = new Watcher($storage);
360
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
361
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
362
-		}
363
-		return $this->watcher;
364
-	}
365
-
366
-	/**
367
-	 * get a propagator instance for the cache
368
-	 *
369
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
370
-	 * @return \OC\Files\Cache\Propagator
371
-	 */
372
-	public function getPropagator($storage = null) {
373
-		if (!$storage) {
374
-			$storage = $this;
375
-		}
376
-		if (!isset($storage->propagator)) {
377
-			$config = \OC::$server->getSystemConfig();
378
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
379
-		}
380
-		return $storage->propagator;
381
-	}
382
-
383
-	public function getUpdater($storage = null) {
384
-		if (!$storage) {
385
-			$storage = $this;
386
-		}
387
-		if (!isset($storage->updater)) {
388
-			$storage->updater = new Updater($storage);
389
-		}
390
-		return $storage->updater;
391
-	}
392
-
393
-	public function getStorageCache($storage = null) {
394
-		if (!$storage) {
395
-			$storage = $this;
396
-		}
397
-		if (!isset($this->storageCache)) {
398
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
399
-		}
400
-		return $this->storageCache;
401
-	}
402
-
403
-	/**
404
-	 * get the owner of a path
405
-	 *
406
-	 * @param string $path The path to get the owner
407
-	 * @return string|false uid or false
408
-	 */
409
-	public function getOwner($path) {
410
-		if ($this->owner === null) {
411
-			$this->owner = \OC_User::getUser();
412
-		}
413
-
414
-		return $this->owner;
415
-	}
416
-
417
-	/**
418
-	 * get the ETag for a file or folder
419
-	 *
420
-	 * @param string $path
421
-	 * @return string
422
-	 */
423
-	public function getETag($path) {
424
-		return uniqid();
425
-	}
426
-
427
-	/**
428
-	 * clean a path, i.e. remove all redundant '.' and '..'
429
-	 * making sure that it can't point to higher than '/'
430
-	 *
431
-	 * @param string $path The path to clean
432
-	 * @return string cleaned path
433
-	 */
434
-	public function cleanPath($path) {
435
-		if (strlen($path) == 0 or $path[0] != '/') {
436
-			$path = '/' . $path;
437
-		}
438
-
439
-		$output = [];
440
-		foreach (explode('/', $path) as $chunk) {
441
-			if ($chunk == '..') {
442
-				array_pop($output);
443
-			} elseif ($chunk == '.') {
444
-			} else {
445
-				$output[] = $chunk;
446
-			}
447
-		}
448
-		return implode('/', $output);
449
-	}
450
-
451
-	/**
452
-	 * Test a storage for availability
453
-	 *
454
-	 * @return bool
455
-	 */
456
-	public function test() {
457
-		try {
458
-			if ($this->stat('')) {
459
-				return true;
460
-			}
461
-			\OC::$server->getLogger()->info("External storage not available: stat() failed");
462
-			return false;
463
-		} catch (\Exception $e) {
464
-			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
465
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
466
-			return false;
467
-		}
468
-	}
469
-
470
-	/**
471
-	 * get the free space in the storage
472
-	 *
473
-	 * @param string $path
474
-	 * @return int|false
475
-	 */
476
-	public function free_space($path) {
477
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
478
-	}
479
-
480
-	/**
481
-	 * {@inheritdoc}
482
-	 */
483
-	public function isLocal() {
484
-		// the common implementation returns a temporary file by
485
-		// default, which is not local
486
-		return false;
487
-	}
488
-
489
-	/**
490
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
491
-	 *
492
-	 * @param string $class
493
-	 * @return bool
494
-	 */
495
-	public function instanceOfStorage($class) {
496
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
497
-			// FIXME Temporary fix to keep existing checks working
498
-			$class = '\OCA\Files_Sharing\SharedStorage';
499
-		}
500
-		return is_a($this, $class);
501
-	}
502
-
503
-	/**
504
-	 * A custom storage implementation can return an url for direct download of a give file.
505
-	 *
506
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
507
-	 *
508
-	 * @param string $path
509
-	 * @return array|false
510
-	 */
511
-	public function getDirectDownload($path) {
512
-		return [];
513
-	}
514
-
515
-	/**
516
-	 * @inheritdoc
517
-	 * @throws InvalidPathException
518
-	 */
519
-	public function verifyPath($path, $fileName) {
520
-
521
-		// verify empty and dot files
522
-		$trimmed = trim($fileName);
523
-		if ($trimmed === '') {
524
-			throw new EmptyFileNameException();
525
-		}
526
-
527
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
528
-			throw new InvalidDirectoryException();
529
-		}
530
-
531
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
532
-			// verify database - e.g. mysql only 3-byte chars
533
-			if (preg_match('%(?:
79
+    use LocalTempFileTrait;
80
+
81
+    protected $cache;
82
+    protected $scanner;
83
+    protected $watcher;
84
+    protected $propagator;
85
+    protected $storageCache;
86
+    protected $updater;
87
+
88
+    protected $mountOptions = [];
89
+    protected $owner = null;
90
+
91
+    private $shouldLogLocks = null;
92
+    private $logger;
93
+
94
+    public function __construct($parameters) {
95
+    }
96
+
97
+    /**
98
+     * Remove a file or folder
99
+     *
100
+     * @param string $path
101
+     * @return bool
102
+     */
103
+    protected function remove($path) {
104
+        if ($this->is_dir($path)) {
105
+            return $this->rmdir($path);
106
+        } elseif ($this->is_file($path)) {
107
+            return $this->unlink($path);
108
+        } else {
109
+            return false;
110
+        }
111
+    }
112
+
113
+    public function is_dir($path) {
114
+        return $this->filetype($path) === 'dir';
115
+    }
116
+
117
+    public function is_file($path) {
118
+        return $this->filetype($path) === 'file';
119
+    }
120
+
121
+    public function filesize($path) {
122
+        if ($this->is_dir($path)) {
123
+            return 0; //by definition
124
+        } else {
125
+            $stat = $this->stat($path);
126
+            if (isset($stat['size'])) {
127
+                return $stat['size'];
128
+            } else {
129
+                return 0;
130
+            }
131
+        }
132
+    }
133
+
134
+    public function isReadable($path) {
135
+        // at least check whether it exists
136
+        // subclasses might want to implement this more thoroughly
137
+        return $this->file_exists($path);
138
+    }
139
+
140
+    public function isUpdatable($path) {
141
+        // at least check whether it exists
142
+        // subclasses might want to implement this more thoroughly
143
+        // a non-existing file/folder isn't updatable
144
+        return $this->file_exists($path);
145
+    }
146
+
147
+    public function isCreatable($path) {
148
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
149
+            return true;
150
+        }
151
+        return false;
152
+    }
153
+
154
+    public function isDeletable($path) {
155
+        if ($path === '' || $path === '/') {
156
+            return false;
157
+        }
158
+        $parent = dirname($path);
159
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
160
+    }
161
+
162
+    public function isSharable($path) {
163
+        return $this->isReadable($path);
164
+    }
165
+
166
+    public function getPermissions($path) {
167
+        $permissions = 0;
168
+        if ($this->isCreatable($path)) {
169
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
170
+        }
171
+        if ($this->isReadable($path)) {
172
+            $permissions |= \OCP\Constants::PERMISSION_READ;
173
+        }
174
+        if ($this->isUpdatable($path)) {
175
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
176
+        }
177
+        if ($this->isDeletable($path)) {
178
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
179
+        }
180
+        if ($this->isSharable($path)) {
181
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
182
+        }
183
+        return $permissions;
184
+    }
185
+
186
+    public function filemtime($path) {
187
+        $stat = $this->stat($path);
188
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
189
+            return $stat['mtime'];
190
+        } else {
191
+            return 0;
192
+        }
193
+    }
194
+
195
+    public function file_get_contents($path) {
196
+        $handle = $this->fopen($path, "r");
197
+        if (!$handle) {
198
+            return false;
199
+        }
200
+        $data = stream_get_contents($handle);
201
+        fclose($handle);
202
+        return $data;
203
+    }
204
+
205
+    public function file_put_contents($path, $data) {
206
+        $handle = $this->fopen($path, "w");
207
+        $this->removeCachedFile($path);
208
+        $count = fwrite($handle, $data);
209
+        fclose($handle);
210
+        return $count;
211
+    }
212
+
213
+    public function rename($path1, $path2) {
214
+        $this->remove($path2);
215
+
216
+        $this->removeCachedFile($path1);
217
+        return $this->copy($path1, $path2) and $this->remove($path1);
218
+    }
219
+
220
+    public function copy($path1, $path2) {
221
+        if ($this->is_dir($path1)) {
222
+            $this->remove($path2);
223
+            $dir = $this->opendir($path1);
224
+            $this->mkdir($path2);
225
+            while ($file = readdir($dir)) {
226
+                if (!Filesystem::isIgnoredDir($file)) {
227
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
+                        return false;
229
+                    }
230
+                }
231
+            }
232
+            closedir($dir);
233
+            return true;
234
+        } else {
235
+            $source = $this->fopen($path1, 'r');
236
+            $target = $this->fopen($path2, 'w');
237
+            [, $result] = \OC_Helper::streamCopy($source, $target);
238
+            if (!$result) {
239
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
240
+            }
241
+            $this->removeCachedFile($path2);
242
+            return $result;
243
+        }
244
+    }
245
+
246
+    public function getMimeType($path) {
247
+        if ($this->is_dir($path)) {
248
+            return 'httpd/unix-directory';
249
+        } elseif ($this->file_exists($path)) {
250
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
251
+        } else {
252
+            return false;
253
+        }
254
+    }
255
+
256
+    public function hash($type, $path, $raw = false) {
257
+        $fh = $this->fopen($path, 'rb');
258
+        $ctx = hash_init($type);
259
+        hash_update_stream($ctx, $fh);
260
+        fclose($fh);
261
+        return hash_final($ctx, $raw);
262
+    }
263
+
264
+    public function search($query) {
265
+        return $this->searchInDir($query);
266
+    }
267
+
268
+    public function getLocalFile($path) {
269
+        return $this->getCachedFile($path);
270
+    }
271
+
272
+    /**
273
+     * @param string $path
274
+     * @param string $target
275
+     */
276
+    private function addLocalFolder($path, $target) {
277
+        $dh = $this->opendir($path);
278
+        if (is_resource($dh)) {
279
+            while (($file = readdir($dh)) !== false) {
280
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
281
+                    if ($this->is_dir($path . '/' . $file)) {
282
+                        mkdir($target . '/' . $file);
283
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
284
+                    } else {
285
+                        $tmp = $this->toTmpFile($path . '/' . $file);
286
+                        rename($tmp, $target . '/' . $file);
287
+                    }
288
+                }
289
+            }
290
+        }
291
+    }
292
+
293
+    /**
294
+     * @param string $query
295
+     * @param string $dir
296
+     * @return array
297
+     */
298
+    protected function searchInDir($query, $dir = '') {
299
+        $files = [];
300
+        $dh = $this->opendir($dir);
301
+        if (is_resource($dh)) {
302
+            while (($item = readdir($dh)) !== false) {
303
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) {
304
+                    continue;
305
+                }
306
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
307
+                    $files[] = $dir . '/' . $item;
308
+                }
309
+                if ($this->is_dir($dir . '/' . $item)) {
310
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
311
+                }
312
+            }
313
+        }
314
+        closedir($dh);
315
+        return $files;
316
+    }
317
+
318
+    /**
319
+     * check if a file or folder has been updated since $time
320
+     *
321
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
322
+     * the mtime should always return false here. As a result storage implementations that always return false expect
323
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
324
+     * ownClouds filesystem.
325
+     *
326
+     * @param string $path
327
+     * @param int $time
328
+     * @return bool
329
+     */
330
+    public function hasUpdated($path, $time) {
331
+        return $this->filemtime($path) > $time;
332
+    }
333
+
334
+    public function getCache($path = '', $storage = null) {
335
+        if (!$storage) {
336
+            $storage = $this;
337
+        }
338
+        if (!isset($storage->cache)) {
339
+            $storage->cache = new Cache($storage);
340
+        }
341
+        return $storage->cache;
342
+    }
343
+
344
+    public function getScanner($path = '', $storage = null) {
345
+        if (!$storage) {
346
+            $storage = $this;
347
+        }
348
+        if (!isset($storage->scanner)) {
349
+            $storage->scanner = new Scanner($storage);
350
+        }
351
+        return $storage->scanner;
352
+    }
353
+
354
+    public function getWatcher($path = '', $storage = null) {
355
+        if (!$storage) {
356
+            $storage = $this;
357
+        }
358
+        if (!isset($this->watcher)) {
359
+            $this->watcher = new Watcher($storage);
360
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
361
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
362
+        }
363
+        return $this->watcher;
364
+    }
365
+
366
+    /**
367
+     * get a propagator instance for the cache
368
+     *
369
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
370
+     * @return \OC\Files\Cache\Propagator
371
+     */
372
+    public function getPropagator($storage = null) {
373
+        if (!$storage) {
374
+            $storage = $this;
375
+        }
376
+        if (!isset($storage->propagator)) {
377
+            $config = \OC::$server->getSystemConfig();
378
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
379
+        }
380
+        return $storage->propagator;
381
+    }
382
+
383
+    public function getUpdater($storage = null) {
384
+        if (!$storage) {
385
+            $storage = $this;
386
+        }
387
+        if (!isset($storage->updater)) {
388
+            $storage->updater = new Updater($storage);
389
+        }
390
+        return $storage->updater;
391
+    }
392
+
393
+    public function getStorageCache($storage = null) {
394
+        if (!$storage) {
395
+            $storage = $this;
396
+        }
397
+        if (!isset($this->storageCache)) {
398
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
399
+        }
400
+        return $this->storageCache;
401
+    }
402
+
403
+    /**
404
+     * get the owner of a path
405
+     *
406
+     * @param string $path The path to get the owner
407
+     * @return string|false uid or false
408
+     */
409
+    public function getOwner($path) {
410
+        if ($this->owner === null) {
411
+            $this->owner = \OC_User::getUser();
412
+        }
413
+
414
+        return $this->owner;
415
+    }
416
+
417
+    /**
418
+     * get the ETag for a file or folder
419
+     *
420
+     * @param string $path
421
+     * @return string
422
+     */
423
+    public function getETag($path) {
424
+        return uniqid();
425
+    }
426
+
427
+    /**
428
+     * clean a path, i.e. remove all redundant '.' and '..'
429
+     * making sure that it can't point to higher than '/'
430
+     *
431
+     * @param string $path The path to clean
432
+     * @return string cleaned path
433
+     */
434
+    public function cleanPath($path) {
435
+        if (strlen($path) == 0 or $path[0] != '/') {
436
+            $path = '/' . $path;
437
+        }
438
+
439
+        $output = [];
440
+        foreach (explode('/', $path) as $chunk) {
441
+            if ($chunk == '..') {
442
+                array_pop($output);
443
+            } elseif ($chunk == '.') {
444
+            } else {
445
+                $output[] = $chunk;
446
+            }
447
+        }
448
+        return implode('/', $output);
449
+    }
450
+
451
+    /**
452
+     * Test a storage for availability
453
+     *
454
+     * @return bool
455
+     */
456
+    public function test() {
457
+        try {
458
+            if ($this->stat('')) {
459
+                return true;
460
+            }
461
+            \OC::$server->getLogger()->info("External storage not available: stat() failed");
462
+            return false;
463
+        } catch (\Exception $e) {
464
+            \OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
465
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
466
+            return false;
467
+        }
468
+    }
469
+
470
+    /**
471
+     * get the free space in the storage
472
+     *
473
+     * @param string $path
474
+     * @return int|false
475
+     */
476
+    public function free_space($path) {
477
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
478
+    }
479
+
480
+    /**
481
+     * {@inheritdoc}
482
+     */
483
+    public function isLocal() {
484
+        // the common implementation returns a temporary file by
485
+        // default, which is not local
486
+        return false;
487
+    }
488
+
489
+    /**
490
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
491
+     *
492
+     * @param string $class
493
+     * @return bool
494
+     */
495
+    public function instanceOfStorage($class) {
496
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
497
+            // FIXME Temporary fix to keep existing checks working
498
+            $class = '\OCA\Files_Sharing\SharedStorage';
499
+        }
500
+        return is_a($this, $class);
501
+    }
502
+
503
+    /**
504
+     * A custom storage implementation can return an url for direct download of a give file.
505
+     *
506
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
507
+     *
508
+     * @param string $path
509
+     * @return array|false
510
+     */
511
+    public function getDirectDownload($path) {
512
+        return [];
513
+    }
514
+
515
+    /**
516
+     * @inheritdoc
517
+     * @throws InvalidPathException
518
+     */
519
+    public function verifyPath($path, $fileName) {
520
+
521
+        // verify empty and dot files
522
+        $trimmed = trim($fileName);
523
+        if ($trimmed === '') {
524
+            throw new EmptyFileNameException();
525
+        }
526
+
527
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
528
+            throw new InvalidDirectoryException();
529
+        }
530
+
531
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
532
+            // verify database - e.g. mysql only 3-byte chars
533
+            if (preg_match('%(?:
534 534
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
535 535
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
536 536
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
537 537
 )%xs', $fileName)) {
538
-				throw new InvalidCharacterInPathException();
539
-			}
540
-		}
541
-
542
-		// 255 characters is the limit on common file systems (ext/xfs)
543
-		// oc_filecache has a 250 char length limit for the filename
544
-		if (isset($fileName[250])) {
545
-			throw new FileNameTooLongException();
546
-		}
547
-
548
-		// NOTE: $path will remain unverified for now
549
-		$this->verifyPosixPath($fileName);
550
-	}
551
-
552
-	/**
553
-	 * @param string $fileName
554
-	 * @throws InvalidPathException
555
-	 */
556
-	protected function verifyPosixPath($fileName) {
557
-		$fileName = trim($fileName);
558
-		$this->scanForInvalidCharacters($fileName, "\\/");
559
-		$reservedNames = ['*'];
560
-		if (in_array($fileName, $reservedNames)) {
561
-			throw new ReservedWordException();
562
-		}
563
-	}
564
-
565
-	/**
566
-	 * @param string $fileName
567
-	 * @param string $invalidChars
568
-	 * @throws InvalidPathException
569
-	 */
570
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
571
-		foreach (str_split($invalidChars) as $char) {
572
-			if (strpos($fileName, $char) !== false) {
573
-				throw new InvalidCharacterInPathException();
574
-			}
575
-		}
576
-
577
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
578
-		if ($sanitizedFileName !== $fileName) {
579
-			throw new InvalidCharacterInPathException();
580
-		}
581
-	}
582
-
583
-	/**
584
-	 * @param array $options
585
-	 */
586
-	public function setMountOptions(array $options) {
587
-		$this->mountOptions = $options;
588
-	}
589
-
590
-	/**
591
-	 * @param string $name
592
-	 * @param mixed $default
593
-	 * @return mixed
594
-	 */
595
-	public function getMountOption($name, $default = null) {
596
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
597
-	}
598
-
599
-	/**
600
-	 * @param IStorage $sourceStorage
601
-	 * @param string $sourceInternalPath
602
-	 * @param string $targetInternalPath
603
-	 * @param bool $preserveMtime
604
-	 * @return bool
605
-	 */
606
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
607
-		if ($sourceStorage === $this) {
608
-			return $this->copy($sourceInternalPath, $targetInternalPath);
609
-		}
610
-
611
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
612
-			$dh = $sourceStorage->opendir($sourceInternalPath);
613
-			$result = $this->mkdir($targetInternalPath);
614
-			if (is_resource($dh)) {
615
-				while ($result and ($file = readdir($dh)) !== false) {
616
-					if (!Filesystem::isIgnoredDir($file)) {
617
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
618
-					}
619
-				}
620
-			}
621
-		} else {
622
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
623
-			$result = false;
624
-			if ($source) {
625
-				try {
626
-					$this->writeStream($targetInternalPath, $source);
627
-					$result = true;
628
-				} catch (\Exception $e) {
629
-					\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
630
-				}
631
-			}
632
-
633
-			if ($result && $preserveMtime) {
634
-				$mtime = $sourceStorage->filemtime($sourceInternalPath);
635
-				$this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
636
-			}
637
-
638
-			if (!$result) {
639
-				// delete partially written target file
640
-				$this->unlink($targetInternalPath);
641
-				// delete cache entry that was created by fopen
642
-				$this->getCache()->remove($targetInternalPath);
643
-			}
644
-		}
645
-		return (bool)$result;
646
-	}
647
-
648
-	/**
649
-	 * Check if a storage is the same as the current one, including wrapped storages
650
-	 *
651
-	 * @param IStorage $storage
652
-	 * @return bool
653
-	 */
654
-	private function isSameStorage(IStorage $storage): bool {
655
-		while ($storage->instanceOfStorage(Wrapper::class)) {
656
-			/**
657
-			 * @var Wrapper $sourceStorage
658
-			 */
659
-			$storage = $storage->getWrapperStorage();
660
-		}
661
-
662
-		return $storage === $this;
663
-	}
664
-
665
-	/**
666
-	 * @param IStorage $sourceStorage
667
-	 * @param string $sourceInternalPath
668
-	 * @param string $targetInternalPath
669
-	 * @return bool
670
-	 */
671
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
672
-		if ($this->isSameStorage($sourceStorage)) {
673
-			// resolve any jailed paths
674
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
675
-				/**
676
-				 * @var Jail $sourceStorage
677
-				 */
678
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
679
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
680
-			}
681
-
682
-			return $this->rename($sourceInternalPath, $targetInternalPath);
683
-		}
684
-
685
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
686
-			return false;
687
-		}
688
-
689
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
690
-		if ($result) {
691
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
692
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
693
-			} else {
694
-				$result &= $sourceStorage->unlink($sourceInternalPath);
695
-			}
696
-		}
697
-		return $result;
698
-	}
699
-
700
-	/**
701
-	 * @inheritdoc
702
-	 */
703
-	public function getMetaData($path) {
704
-		$permissions = $this->getPermissions($path);
705
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
706
-			//can't read, nothing we can do
707
-			return null;
708
-		}
709
-
710
-		$data = [];
711
-		$data['mimetype'] = $this->getMimeType($path);
712
-		$data['mtime'] = $this->filemtime($path);
713
-		if ($data['mtime'] === false) {
714
-			$data['mtime'] = time();
715
-		}
716
-		if ($data['mimetype'] == 'httpd/unix-directory') {
717
-			$data['size'] = -1; //unknown
718
-		} else {
719
-			$data['size'] = $this->filesize($path);
720
-		}
721
-		$data['etag'] = $this->getETag($path);
722
-		$data['storage_mtime'] = $data['mtime'];
723
-		$data['permissions'] = $permissions;
724
-		$data['name'] = basename($path);
725
-
726
-		return $data;
727
-	}
728
-
729
-	/**
730
-	 * @param string $path
731
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
732
-	 * @param \OCP\Lock\ILockingProvider $provider
733
-	 * @throws \OCP\Lock\LockedException
734
-	 */
735
-	public function acquireLock($path, $type, ILockingProvider $provider) {
736
-		$logger = $this->getLockLogger();
737
-		if ($logger) {
738
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
739
-			$logger->info(
740
-				sprintf(
741
-					'acquire %s lock on "%s" on storage "%s"',
742
-					$typeString,
743
-					$path,
744
-					$this->getId()
745
-				),
746
-				[
747
-					'app' => 'locking',
748
-				]
749
-			);
750
-		}
751
-		try {
752
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
753
-		} catch (LockedException $e) {
754
-			if ($logger) {
755
-				$logger->logException($e, ['level' => ILogger::INFO]);
756
-			}
757
-			throw $e;
758
-		}
759
-	}
760
-
761
-	/**
762
-	 * @param string $path
763
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
764
-	 * @param \OCP\Lock\ILockingProvider $provider
765
-	 * @throws \OCP\Lock\LockedException
766
-	 */
767
-	public function releaseLock($path, $type, ILockingProvider $provider) {
768
-		$logger = $this->getLockLogger();
769
-		if ($logger) {
770
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
771
-			$logger->info(
772
-				sprintf(
773
-					'release %s lock on "%s" on storage "%s"',
774
-					$typeString,
775
-					$path,
776
-					$this->getId()
777
-				),
778
-				[
779
-					'app' => 'locking',
780
-				]
781
-			);
782
-		}
783
-		try {
784
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
785
-		} catch (LockedException $e) {
786
-			if ($logger) {
787
-				$logger->logException($e, ['level' => ILogger::INFO]);
788
-			}
789
-			throw $e;
790
-		}
791
-	}
792
-
793
-	/**
794
-	 * @param string $path
795
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
796
-	 * @param \OCP\Lock\ILockingProvider $provider
797
-	 * @throws \OCP\Lock\LockedException
798
-	 */
799
-	public function changeLock($path, $type, ILockingProvider $provider) {
800
-		$logger = $this->getLockLogger();
801
-		if ($logger) {
802
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
803
-			$logger->info(
804
-				sprintf(
805
-					'change lock on "%s" to %s on storage "%s"',
806
-					$path,
807
-					$typeString,
808
-					$this->getId()
809
-				),
810
-				[
811
-					'app' => 'locking',
812
-				]
813
-			);
814
-		}
815
-		try {
816
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
817
-		} catch (LockedException $e) {
818
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
819
-			throw $e;
820
-		}
821
-	}
822
-
823
-	private function getLockLogger() {
824
-		if (is_null($this->shouldLogLocks)) {
825
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
826
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
827
-		}
828
-		return $this->logger;
829
-	}
830
-
831
-	/**
832
-	 * @return array [ available, last_checked ]
833
-	 */
834
-	public function getAvailability() {
835
-		return $this->getStorageCache()->getAvailability();
836
-	}
837
-
838
-	/**
839
-	 * @param bool $isAvailable
840
-	 */
841
-	public function setAvailability($isAvailable) {
842
-		$this->getStorageCache()->setAvailability($isAvailable);
843
-	}
844
-
845
-	/**
846
-	 * @return bool
847
-	 */
848
-	public function needsPartFile() {
849
-		return true;
850
-	}
851
-
852
-	/**
853
-	 * fallback implementation
854
-	 *
855
-	 * @param string $path
856
-	 * @param resource $stream
857
-	 * @param int $size
858
-	 * @return int
859
-	 */
860
-	public function writeStream(string $path, $stream, int $size = null): int {
861
-		$target = $this->fopen($path, 'w');
862
-		if (!$target) {
863
-			throw new GenericFileException("Failed to open $path for writing");
864
-		}
865
-		try {
866
-			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
867
-			if (!$result) {
868
-				throw new GenericFileException("Failed to copy stream");
869
-			}
870
-		} finally {
871
-			fclose($target);
872
-			fclose($stream);
873
-		}
874
-		return $count;
875
-	}
876
-
877
-	public function getDirectoryContent($directory): \Traversable {
878
-		$dh = $this->opendir($directory);
879
-		if (is_resource($dh)) {
880
-			$basePath = rtrim($directory, '/');
881
-			while (($file = readdir($dh)) !== false) {
882
-				if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) {
883
-					$childPath = $basePath . '/' . trim($file, '/');
884
-					$metadata = $this->getMetaData($childPath);
885
-					if ($metadata !== null) {
886
-						yield $metadata;
887
-					}
888
-				}
889
-			}
890
-		}
891
-	}
538
+                throw new InvalidCharacterInPathException();
539
+            }
540
+        }
541
+
542
+        // 255 characters is the limit on common file systems (ext/xfs)
543
+        // oc_filecache has a 250 char length limit for the filename
544
+        if (isset($fileName[250])) {
545
+            throw new FileNameTooLongException();
546
+        }
547
+
548
+        // NOTE: $path will remain unverified for now
549
+        $this->verifyPosixPath($fileName);
550
+    }
551
+
552
+    /**
553
+     * @param string $fileName
554
+     * @throws InvalidPathException
555
+     */
556
+    protected function verifyPosixPath($fileName) {
557
+        $fileName = trim($fileName);
558
+        $this->scanForInvalidCharacters($fileName, "\\/");
559
+        $reservedNames = ['*'];
560
+        if (in_array($fileName, $reservedNames)) {
561
+            throw new ReservedWordException();
562
+        }
563
+    }
564
+
565
+    /**
566
+     * @param string $fileName
567
+     * @param string $invalidChars
568
+     * @throws InvalidPathException
569
+     */
570
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
571
+        foreach (str_split($invalidChars) as $char) {
572
+            if (strpos($fileName, $char) !== false) {
573
+                throw new InvalidCharacterInPathException();
574
+            }
575
+        }
576
+
577
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
578
+        if ($sanitizedFileName !== $fileName) {
579
+            throw new InvalidCharacterInPathException();
580
+        }
581
+    }
582
+
583
+    /**
584
+     * @param array $options
585
+     */
586
+    public function setMountOptions(array $options) {
587
+        $this->mountOptions = $options;
588
+    }
589
+
590
+    /**
591
+     * @param string $name
592
+     * @param mixed $default
593
+     * @return mixed
594
+     */
595
+    public function getMountOption($name, $default = null) {
596
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
597
+    }
598
+
599
+    /**
600
+     * @param IStorage $sourceStorage
601
+     * @param string $sourceInternalPath
602
+     * @param string $targetInternalPath
603
+     * @param bool $preserveMtime
604
+     * @return bool
605
+     */
606
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
607
+        if ($sourceStorage === $this) {
608
+            return $this->copy($sourceInternalPath, $targetInternalPath);
609
+        }
610
+
611
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
612
+            $dh = $sourceStorage->opendir($sourceInternalPath);
613
+            $result = $this->mkdir($targetInternalPath);
614
+            if (is_resource($dh)) {
615
+                while ($result and ($file = readdir($dh)) !== false) {
616
+                    if (!Filesystem::isIgnoredDir($file)) {
617
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
618
+                    }
619
+                }
620
+            }
621
+        } else {
622
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
623
+            $result = false;
624
+            if ($source) {
625
+                try {
626
+                    $this->writeStream($targetInternalPath, $source);
627
+                    $result = true;
628
+                } catch (\Exception $e) {
629
+                    \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
630
+                }
631
+            }
632
+
633
+            if ($result && $preserveMtime) {
634
+                $mtime = $sourceStorage->filemtime($sourceInternalPath);
635
+                $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
636
+            }
637
+
638
+            if (!$result) {
639
+                // delete partially written target file
640
+                $this->unlink($targetInternalPath);
641
+                // delete cache entry that was created by fopen
642
+                $this->getCache()->remove($targetInternalPath);
643
+            }
644
+        }
645
+        return (bool)$result;
646
+    }
647
+
648
+    /**
649
+     * Check if a storage is the same as the current one, including wrapped storages
650
+     *
651
+     * @param IStorage $storage
652
+     * @return bool
653
+     */
654
+    private function isSameStorage(IStorage $storage): bool {
655
+        while ($storage->instanceOfStorage(Wrapper::class)) {
656
+            /**
657
+             * @var Wrapper $sourceStorage
658
+             */
659
+            $storage = $storage->getWrapperStorage();
660
+        }
661
+
662
+        return $storage === $this;
663
+    }
664
+
665
+    /**
666
+     * @param IStorage $sourceStorage
667
+     * @param string $sourceInternalPath
668
+     * @param string $targetInternalPath
669
+     * @return bool
670
+     */
671
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
672
+        if ($this->isSameStorage($sourceStorage)) {
673
+            // resolve any jailed paths
674
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
675
+                /**
676
+                 * @var Jail $sourceStorage
677
+                 */
678
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
679
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
680
+            }
681
+
682
+            return $this->rename($sourceInternalPath, $targetInternalPath);
683
+        }
684
+
685
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
686
+            return false;
687
+        }
688
+
689
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
690
+        if ($result) {
691
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
692
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
693
+            } else {
694
+                $result &= $sourceStorage->unlink($sourceInternalPath);
695
+            }
696
+        }
697
+        return $result;
698
+    }
699
+
700
+    /**
701
+     * @inheritdoc
702
+     */
703
+    public function getMetaData($path) {
704
+        $permissions = $this->getPermissions($path);
705
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
706
+            //can't read, nothing we can do
707
+            return null;
708
+        }
709
+
710
+        $data = [];
711
+        $data['mimetype'] = $this->getMimeType($path);
712
+        $data['mtime'] = $this->filemtime($path);
713
+        if ($data['mtime'] === false) {
714
+            $data['mtime'] = time();
715
+        }
716
+        if ($data['mimetype'] == 'httpd/unix-directory') {
717
+            $data['size'] = -1; //unknown
718
+        } else {
719
+            $data['size'] = $this->filesize($path);
720
+        }
721
+        $data['etag'] = $this->getETag($path);
722
+        $data['storage_mtime'] = $data['mtime'];
723
+        $data['permissions'] = $permissions;
724
+        $data['name'] = basename($path);
725
+
726
+        return $data;
727
+    }
728
+
729
+    /**
730
+     * @param string $path
731
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
732
+     * @param \OCP\Lock\ILockingProvider $provider
733
+     * @throws \OCP\Lock\LockedException
734
+     */
735
+    public function acquireLock($path, $type, ILockingProvider $provider) {
736
+        $logger = $this->getLockLogger();
737
+        if ($logger) {
738
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
739
+            $logger->info(
740
+                sprintf(
741
+                    'acquire %s lock on "%s" on storage "%s"',
742
+                    $typeString,
743
+                    $path,
744
+                    $this->getId()
745
+                ),
746
+                [
747
+                    'app' => 'locking',
748
+                ]
749
+            );
750
+        }
751
+        try {
752
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
753
+        } catch (LockedException $e) {
754
+            if ($logger) {
755
+                $logger->logException($e, ['level' => ILogger::INFO]);
756
+            }
757
+            throw $e;
758
+        }
759
+    }
760
+
761
+    /**
762
+     * @param string $path
763
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
764
+     * @param \OCP\Lock\ILockingProvider $provider
765
+     * @throws \OCP\Lock\LockedException
766
+     */
767
+    public function releaseLock($path, $type, ILockingProvider $provider) {
768
+        $logger = $this->getLockLogger();
769
+        if ($logger) {
770
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
771
+            $logger->info(
772
+                sprintf(
773
+                    'release %s lock on "%s" on storage "%s"',
774
+                    $typeString,
775
+                    $path,
776
+                    $this->getId()
777
+                ),
778
+                [
779
+                    'app' => 'locking',
780
+                ]
781
+            );
782
+        }
783
+        try {
784
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
785
+        } catch (LockedException $e) {
786
+            if ($logger) {
787
+                $logger->logException($e, ['level' => ILogger::INFO]);
788
+            }
789
+            throw $e;
790
+        }
791
+    }
792
+
793
+    /**
794
+     * @param string $path
795
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
796
+     * @param \OCP\Lock\ILockingProvider $provider
797
+     * @throws \OCP\Lock\LockedException
798
+     */
799
+    public function changeLock($path, $type, ILockingProvider $provider) {
800
+        $logger = $this->getLockLogger();
801
+        if ($logger) {
802
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
803
+            $logger->info(
804
+                sprintf(
805
+                    'change lock on "%s" to %s on storage "%s"',
806
+                    $path,
807
+                    $typeString,
808
+                    $this->getId()
809
+                ),
810
+                [
811
+                    'app' => 'locking',
812
+                ]
813
+            );
814
+        }
815
+        try {
816
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
817
+        } catch (LockedException $e) {
818
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
819
+            throw $e;
820
+        }
821
+    }
822
+
823
+    private function getLockLogger() {
824
+        if (is_null($this->shouldLogLocks)) {
825
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
826
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
827
+        }
828
+        return $this->logger;
829
+    }
830
+
831
+    /**
832
+     * @return array [ available, last_checked ]
833
+     */
834
+    public function getAvailability() {
835
+        return $this->getStorageCache()->getAvailability();
836
+    }
837
+
838
+    /**
839
+     * @param bool $isAvailable
840
+     */
841
+    public function setAvailability($isAvailable) {
842
+        $this->getStorageCache()->setAvailability($isAvailable);
843
+    }
844
+
845
+    /**
846
+     * @return bool
847
+     */
848
+    public function needsPartFile() {
849
+        return true;
850
+    }
851
+
852
+    /**
853
+     * fallback implementation
854
+     *
855
+     * @param string $path
856
+     * @param resource $stream
857
+     * @param int $size
858
+     * @return int
859
+     */
860
+    public function writeStream(string $path, $stream, int $size = null): int {
861
+        $target = $this->fopen($path, 'w');
862
+        if (!$target) {
863
+            throw new GenericFileException("Failed to open $path for writing");
864
+        }
865
+        try {
866
+            [$count, $result] = \OC_Helper::streamCopy($stream, $target);
867
+            if (!$result) {
868
+                throw new GenericFileException("Failed to copy stream");
869
+            }
870
+        } finally {
871
+            fclose($target);
872
+            fclose($stream);
873
+        }
874
+        return $count;
875
+    }
876
+
877
+    public function getDirectoryContent($directory): \Traversable {
878
+        $dh = $this->opendir($directory);
879
+        if (is_resource($dh)) {
880
+            $basePath = rtrim($directory, '/');
881
+            while (($file = readdir($dh)) !== false) {
882
+                if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) {
883
+                    $childPath = $basePath . '/' . trim($file, '/');
884
+                    $metadata = $this->getMetaData($childPath);
885
+                    if ($metadata !== null) {
886
+                        yield $metadata;
887
+                    }
888
+                }
889
+            }
890
+        }
891
+    }
892 892
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Quota.php 1 patch
Indentation   +204 added lines, -204 removed lines patch added patch discarded remove patch
@@ -36,208 +36,208 @@
 block discarded – undo
36 36
 
37 37
 class Quota extends Wrapper {
38 38
 
39
-	/**
40
-	 * @var int $quota
41
-	 */
42
-	protected $quota;
43
-
44
-	/**
45
-	 * @var string $sizeRoot
46
-	 */
47
-	protected $sizeRoot;
48
-
49
-	private $config;
50
-
51
-	/**
52
-	 * @param array $parameters
53
-	 */
54
-	public function __construct($parameters) {
55
-		parent::__construct($parameters);
56
-		$this->quota = $parameters['quota'];
57
-		$this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : '';
58
-		$this->config = \OC::$server->getSystemConfig();
59
-	}
60
-
61
-	/**
62
-	 * @return int quota value
63
-	 */
64
-	public function getQuota() {
65
-		return $this->quota;
66
-	}
67
-
68
-	/**
69
-	 * @param string $path
70
-	 * @param \OC\Files\Storage\Storage $storage
71
-	 */
72
-	protected function getSize($path, $storage = null) {
73
-		if ($this->config->getValue('quota_include_external_storage', false)) {
74
-			$rootInfo = Filesystem::getFileInfo('', 'ext');
75
-			if ($rootInfo) {
76
-				return $rootInfo->getSize(true);
77
-			}
78
-			return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
79
-		} else {
80
-			if (is_null($storage)) {
81
-				$cache = $this->getCache();
82
-			} else {
83
-				$cache = $storage->getCache();
84
-			}
85
-			$data = $cache->get($path);
86
-			if ($data instanceof ICacheEntry and isset($data['size'])) {
87
-				return $data['size'];
88
-			} else {
89
-				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
90
-			}
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * Get free space as limited by the quota
96
-	 *
97
-	 * @param string $path
98
-	 * @return int|bool
99
-	 */
100
-	public function free_space($path) {
101
-		if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) {
102
-			return $this->storage->free_space($path);
103
-		} else {
104
-			$used = $this->getSize($this->sizeRoot);
105
-			if ($used < 0) {
106
-				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
107
-			} else {
108
-				$free = $this->storage->free_space($path);
109
-				$quotaFree = max($this->quota - $used, 0);
110
-				// if free space is known
111
-				if ($free >= 0) {
112
-					$free = min($free, $quotaFree);
113
-				} else {
114
-					$free = $quotaFree;
115
-				}
116
-				return $free;
117
-			}
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
123
-	 *
124
-	 * @param string $path
125
-	 * @param mixed $data
126
-	 * @return int|false
127
-	 */
128
-	public function file_put_contents($path, $data) {
129
-		$free = $this->free_space($path);
130
-		if ($free < 0 or strlen($data) < $free) {
131
-			return $this->storage->file_put_contents($path, $data);
132
-		} else {
133
-			return false;
134
-		}
135
-	}
136
-
137
-	/**
138
-	 * see https://www.php.net/manual/en/function.copy.php
139
-	 *
140
-	 * @param string $source
141
-	 * @param string $target
142
-	 * @return bool
143
-	 */
144
-	public function copy($source, $target) {
145
-		$free = $this->free_space($target);
146
-		if ($free < 0 or $this->getSize($source) < $free) {
147
-			return $this->storage->copy($source, $target);
148
-		} else {
149
-			return false;
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * see https://www.php.net/manual/en/function.fopen.php
155
-	 *
156
-	 * @param string $path
157
-	 * @param string $mode
158
-	 * @return resource|bool
159
-	 */
160
-	public function fopen($path, $mode) {
161
-		$source = $this->storage->fopen($path, $mode);
162
-
163
-		// don't apply quota for part files
164
-		if (!$this->isPartFile($path)) {
165
-			$free = $this->free_space($path);
166
-			if ($source && is_int($free) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
167
-				// only apply quota for files, not metadata, trash or others
168
-				if ($this->shouldApplyQuota($path)) {
169
-					return \OC\Files\Stream\Quota::wrap($source, $free);
170
-				}
171
-			}
172
-		}
173
-		return $source;
174
-	}
175
-
176
-	/**
177
-	 * Checks whether the given path is a part file
178
-	 *
179
-	 * @param string $path Path that may identify a .part file
180
-	 * @return string File path without .part extension
181
-	 * @note this is needed for reusing keys
182
-	 */
183
-	private function isPartFile($path) {
184
-		$extension = pathinfo($path, PATHINFO_EXTENSION);
185
-
186
-		return ($extension === 'part');
187
-	}
188
-
189
-	/**
190
-	 * Only apply quota for files, not metadata, trash or others
191
-	 */
192
-	private function shouldApplyQuota(string $path): bool {
193
-		return strpos(ltrim($path, '/'), 'files/') === 0;
194
-	}
195
-
196
-	/**
197
-	 * @param IStorage $sourceStorage
198
-	 * @param string $sourceInternalPath
199
-	 * @param string $targetInternalPath
200
-	 * @return bool
201
-	 */
202
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
203
-		$free = $this->free_space($targetInternalPath);
204
-		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
205
-			return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
206
-		} else {
207
-			return false;
208
-		}
209
-	}
210
-
211
-	/**
212
-	 * @param IStorage $sourceStorage
213
-	 * @param string $sourceInternalPath
214
-	 * @param string $targetInternalPath
215
-	 * @return bool
216
-	 */
217
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
218
-		$free = $this->free_space($targetInternalPath);
219
-		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
220
-			return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
221
-		} else {
222
-			return false;
223
-		}
224
-	}
225
-
226
-	public function mkdir($path) {
227
-		$free = $this->free_space($path);
228
-		if ($this->shouldApplyQuota($path) && $free === 0.0) {
229
-			return false;
230
-		}
231
-
232
-		return parent::mkdir($path);
233
-	}
234
-
235
-	public function touch($path, $mtime = null) {
236
-		$free = $this->free_space($path);
237
-		if ($free === 0.0) {
238
-			return false;
239
-		}
240
-
241
-		return parent::touch($path, $mtime);
242
-	}
39
+    /**
40
+     * @var int $quota
41
+     */
42
+    protected $quota;
43
+
44
+    /**
45
+     * @var string $sizeRoot
46
+     */
47
+    protected $sizeRoot;
48
+
49
+    private $config;
50
+
51
+    /**
52
+     * @param array $parameters
53
+     */
54
+    public function __construct($parameters) {
55
+        parent::__construct($parameters);
56
+        $this->quota = $parameters['quota'];
57
+        $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : '';
58
+        $this->config = \OC::$server->getSystemConfig();
59
+    }
60
+
61
+    /**
62
+     * @return int quota value
63
+     */
64
+    public function getQuota() {
65
+        return $this->quota;
66
+    }
67
+
68
+    /**
69
+     * @param string $path
70
+     * @param \OC\Files\Storage\Storage $storage
71
+     */
72
+    protected function getSize($path, $storage = null) {
73
+        if ($this->config->getValue('quota_include_external_storage', false)) {
74
+            $rootInfo = Filesystem::getFileInfo('', 'ext');
75
+            if ($rootInfo) {
76
+                return $rootInfo->getSize(true);
77
+            }
78
+            return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
79
+        } else {
80
+            if (is_null($storage)) {
81
+                $cache = $this->getCache();
82
+            } else {
83
+                $cache = $storage->getCache();
84
+            }
85
+            $data = $cache->get($path);
86
+            if ($data instanceof ICacheEntry and isset($data['size'])) {
87
+                return $data['size'];
88
+            } else {
89
+                return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
90
+            }
91
+        }
92
+    }
93
+
94
+    /**
95
+     * Get free space as limited by the quota
96
+     *
97
+     * @param string $path
98
+     * @return int|bool
99
+     */
100
+    public function free_space($path) {
101
+        if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) {
102
+            return $this->storage->free_space($path);
103
+        } else {
104
+            $used = $this->getSize($this->sizeRoot);
105
+            if ($used < 0) {
106
+                return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
107
+            } else {
108
+                $free = $this->storage->free_space($path);
109
+                $quotaFree = max($this->quota - $used, 0);
110
+                // if free space is known
111
+                if ($free >= 0) {
112
+                    $free = min($free, $quotaFree);
113
+                } else {
114
+                    $free = $quotaFree;
115
+                }
116
+                return $free;
117
+            }
118
+        }
119
+    }
120
+
121
+    /**
122
+     * see https://www.php.net/manual/en/function.file_put_contents.php
123
+     *
124
+     * @param string $path
125
+     * @param mixed $data
126
+     * @return int|false
127
+     */
128
+    public function file_put_contents($path, $data) {
129
+        $free = $this->free_space($path);
130
+        if ($free < 0 or strlen($data) < $free) {
131
+            return $this->storage->file_put_contents($path, $data);
132
+        } else {
133
+            return false;
134
+        }
135
+    }
136
+
137
+    /**
138
+     * see https://www.php.net/manual/en/function.copy.php
139
+     *
140
+     * @param string $source
141
+     * @param string $target
142
+     * @return bool
143
+     */
144
+    public function copy($source, $target) {
145
+        $free = $this->free_space($target);
146
+        if ($free < 0 or $this->getSize($source) < $free) {
147
+            return $this->storage->copy($source, $target);
148
+        } else {
149
+            return false;
150
+        }
151
+    }
152
+
153
+    /**
154
+     * see https://www.php.net/manual/en/function.fopen.php
155
+     *
156
+     * @param string $path
157
+     * @param string $mode
158
+     * @return resource|bool
159
+     */
160
+    public function fopen($path, $mode) {
161
+        $source = $this->storage->fopen($path, $mode);
162
+
163
+        // don't apply quota for part files
164
+        if (!$this->isPartFile($path)) {
165
+            $free = $this->free_space($path);
166
+            if ($source && is_int($free) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
167
+                // only apply quota for files, not metadata, trash or others
168
+                if ($this->shouldApplyQuota($path)) {
169
+                    return \OC\Files\Stream\Quota::wrap($source, $free);
170
+                }
171
+            }
172
+        }
173
+        return $source;
174
+    }
175
+
176
+    /**
177
+     * Checks whether the given path is a part file
178
+     *
179
+     * @param string $path Path that may identify a .part file
180
+     * @return string File path without .part extension
181
+     * @note this is needed for reusing keys
182
+     */
183
+    private function isPartFile($path) {
184
+        $extension = pathinfo($path, PATHINFO_EXTENSION);
185
+
186
+        return ($extension === 'part');
187
+    }
188
+
189
+    /**
190
+     * Only apply quota for files, not metadata, trash or others
191
+     */
192
+    private function shouldApplyQuota(string $path): bool {
193
+        return strpos(ltrim($path, '/'), 'files/') === 0;
194
+    }
195
+
196
+    /**
197
+     * @param IStorage $sourceStorage
198
+     * @param string $sourceInternalPath
199
+     * @param string $targetInternalPath
200
+     * @return bool
201
+     */
202
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
203
+        $free = $this->free_space($targetInternalPath);
204
+        if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
205
+            return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
206
+        } else {
207
+            return false;
208
+        }
209
+    }
210
+
211
+    /**
212
+     * @param IStorage $sourceStorage
213
+     * @param string $sourceInternalPath
214
+     * @param string $targetInternalPath
215
+     * @return bool
216
+     */
217
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
218
+        $free = $this->free_space($targetInternalPath);
219
+        if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
220
+            return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
221
+        } else {
222
+            return false;
223
+        }
224
+    }
225
+
226
+    public function mkdir($path) {
227
+        $free = $this->free_space($path);
228
+        if ($this->shouldApplyQuota($path) && $free === 0.0) {
229
+            return false;
230
+        }
231
+
232
+        return parent::mkdir($path);
233
+    }
234
+
235
+    public function touch($path, $mtime = null) {
236
+        $free = $this->free_space($path);
237
+        if ($free === 0.0) {
238
+            return false;
239
+        }
240
+
241
+        return parent::touch($path, $mtime);
242
+    }
243 243
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 1 patch
Indentation   +502 added lines, -502 removed lines patch added patch discarded remove patch
@@ -37,506 +37,506 @@
 block discarded – undo
37 37
  */
38 38
 class Encoding extends Wrapper {
39 39
 
40
-	/**
41
-	 * @var ICache
42
-	 */
43
-	private $namesCache;
44
-
45
-	/**
46
-	 * @param array $parameters
47
-	 */
48
-	public function __construct($parameters) {
49
-		$this->storage = $parameters['storage'];
50
-		$this->namesCache = new CappedMemoryCache();
51
-	}
52
-
53
-	/**
54
-	 * Returns whether the given string is only made of ASCII characters
55
-	 *
56
-	 * @param string $str string
57
-	 *
58
-	 * @return bool true if the string is all ASCII, false otherwise
59
-	 */
60
-	private function isAscii($str) {
61
-		return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
62
-	}
63
-
64
-	/**
65
-	 * Checks whether the given path exists in NFC or NFD form after checking
66
-	 * each form for each path section and returns the correct form.
67
-	 * If no existing path found, returns the path as it was given.
68
-	 *
69
-	 * @param string $fullPath path to check
70
-	 *
71
-	 * @return string original or converted path
72
-	 */
73
-	private function findPathToUse($fullPath) {
74
-		$cachedPath = $this->namesCache[$fullPath];
75
-		if ($cachedPath !== null) {
76
-			return $cachedPath;
77
-		}
78
-
79
-		$sections = explode('/', $fullPath);
80
-		$path = '';
81
-		foreach ($sections as $section) {
82
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
83
-			if ($convertedPath === null) {
84
-				// no point in continuing if the section was not found, use original path
85
-				return $fullPath;
86
-			}
87
-			$path = $convertedPath . '/';
88
-		}
89
-		$path = rtrim($path, '/');
90
-		return $path;
91
-	}
92
-
93
-	/**
94
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
95
-	 * and returns the correct form. If no existing path found, returns null.
96
-	 *
97
-	 * @param string $basePath base path to check
98
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
99
-	 *
100
-	 * @return string|null original or converted path, or null if none of the forms was found
101
-	 */
102
-	private function findPathToUseLastSection($basePath, $lastSection) {
103
-		$fullPath = $basePath . $lastSection;
104
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
105
-			$this->namesCache[$fullPath] = $fullPath;
106
-			return $fullPath;
107
-		}
108
-
109
-		// swap encoding
110
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
111
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
112
-		} else {
113
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
114
-		}
115
-		$otherFullPath = $basePath . $otherFormPath;
116
-		if ($this->storage->file_exists($otherFullPath)) {
117
-			$this->namesCache[$fullPath] = $otherFullPath;
118
-			return $otherFullPath;
119
-		}
120
-
121
-		// return original path, file did not exist at all
122
-		$this->namesCache[$fullPath] = $fullPath;
123
-		return null;
124
-	}
125
-
126
-	/**
127
-	 * see https://www.php.net/manual/en/function.mkdir.php
128
-	 *
129
-	 * @param string $path
130
-	 * @return bool
131
-	 */
132
-	public function mkdir($path) {
133
-		// note: no conversion here, method should not be called with non-NFC names!
134
-		$result = $this->storage->mkdir($path);
135
-		if ($result) {
136
-			$this->namesCache[$path] = $path;
137
-		}
138
-		return $result;
139
-	}
140
-
141
-	/**
142
-	 * see https://www.php.net/manual/en/function.rmdir.php
143
-	 *
144
-	 * @param string $path
145
-	 * @return bool
146
-	 */
147
-	public function rmdir($path) {
148
-		$result = $this->storage->rmdir($this->findPathToUse($path));
149
-		if ($result) {
150
-			unset($this->namesCache[$path]);
151
-		}
152
-		return $result;
153
-	}
154
-
155
-	/**
156
-	 * see https://www.php.net/manual/en/function.opendir.php
157
-	 *
158
-	 * @param string $path
159
-	 * @return resource|bool
160
-	 */
161
-	public function opendir($path) {
162
-		return $this->storage->opendir($this->findPathToUse($path));
163
-	}
164
-
165
-	/**
166
-	 * see https://www.php.net/manual/en/function.is_dir.php
167
-	 *
168
-	 * @param string $path
169
-	 * @return bool
170
-	 */
171
-	public function is_dir($path) {
172
-		return $this->storage->is_dir($this->findPathToUse($path));
173
-	}
174
-
175
-	/**
176
-	 * see https://www.php.net/manual/en/function.is_file.php
177
-	 *
178
-	 * @param string $path
179
-	 * @return bool
180
-	 */
181
-	public function is_file($path) {
182
-		return $this->storage->is_file($this->findPathToUse($path));
183
-	}
184
-
185
-	/**
186
-	 * see https://www.php.net/manual/en/function.stat.php
187
-	 * only the following keys are required in the result: size and mtime
188
-	 *
189
-	 * @param string $path
190
-	 * @return array|bool
191
-	 */
192
-	public function stat($path) {
193
-		return $this->storage->stat($this->findPathToUse($path));
194
-	}
195
-
196
-	/**
197
-	 * see https://www.php.net/manual/en/function.filetype.php
198
-	 *
199
-	 * @param string $path
200
-	 * @return string|bool
201
-	 */
202
-	public function filetype($path) {
203
-		return $this->storage->filetype($this->findPathToUse($path));
204
-	}
205
-
206
-	/**
207
-	 * see https://www.php.net/manual/en/function.filesize.php
208
-	 * The result for filesize when called on a folder is required to be 0
209
-	 *
210
-	 * @param string $path
211
-	 * @return int|bool
212
-	 */
213
-	public function filesize($path) {
214
-		return $this->storage->filesize($this->findPathToUse($path));
215
-	}
216
-
217
-	/**
218
-	 * check if a file can be created in $path
219
-	 *
220
-	 * @param string $path
221
-	 * @return bool
222
-	 */
223
-	public function isCreatable($path) {
224
-		return $this->storage->isCreatable($this->findPathToUse($path));
225
-	}
226
-
227
-	/**
228
-	 * check if a file can be read
229
-	 *
230
-	 * @param string $path
231
-	 * @return bool
232
-	 */
233
-	public function isReadable($path) {
234
-		return $this->storage->isReadable($this->findPathToUse($path));
235
-	}
236
-
237
-	/**
238
-	 * check if a file can be written to
239
-	 *
240
-	 * @param string $path
241
-	 * @return bool
242
-	 */
243
-	public function isUpdatable($path) {
244
-		return $this->storage->isUpdatable($this->findPathToUse($path));
245
-	}
246
-
247
-	/**
248
-	 * check if a file can be deleted
249
-	 *
250
-	 * @param string $path
251
-	 * @return bool
252
-	 */
253
-	public function isDeletable($path) {
254
-		return $this->storage->isDeletable($this->findPathToUse($path));
255
-	}
256
-
257
-	/**
258
-	 * check if a file can be shared
259
-	 *
260
-	 * @param string $path
261
-	 * @return bool
262
-	 */
263
-	public function isSharable($path) {
264
-		return $this->storage->isSharable($this->findPathToUse($path));
265
-	}
266
-
267
-	/**
268
-	 * get the full permissions of a path.
269
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
270
-	 *
271
-	 * @param string $path
272
-	 * @return int
273
-	 */
274
-	public function getPermissions($path) {
275
-		return $this->storage->getPermissions($this->findPathToUse($path));
276
-	}
277
-
278
-	/**
279
-	 * see https://www.php.net/manual/en/function.file_exists.php
280
-	 *
281
-	 * @param string $path
282
-	 * @return bool
283
-	 */
284
-	public function file_exists($path) {
285
-		return $this->storage->file_exists($this->findPathToUse($path));
286
-	}
287
-
288
-	/**
289
-	 * see https://www.php.net/manual/en/function.filemtime.php
290
-	 *
291
-	 * @param string $path
292
-	 * @return int|bool
293
-	 */
294
-	public function filemtime($path) {
295
-		return $this->storage->filemtime($this->findPathToUse($path));
296
-	}
297
-
298
-	/**
299
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
300
-	 *
301
-	 * @param string $path
302
-	 * @return string|bool
303
-	 */
304
-	public function file_get_contents($path) {
305
-		return $this->storage->file_get_contents($this->findPathToUse($path));
306
-	}
307
-
308
-	/**
309
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
310
-	 *
311
-	 * @param string $path
312
-	 * @param mixed $data
313
-	 * @return int|false
314
-	 */
315
-	public function file_put_contents($path, $data) {
316
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
317
-	}
318
-
319
-	/**
320
-	 * see https://www.php.net/manual/en/function.unlink.php
321
-	 *
322
-	 * @param string $path
323
-	 * @return bool
324
-	 */
325
-	public function unlink($path) {
326
-		$result = $this->storage->unlink($this->findPathToUse($path));
327
-		if ($result) {
328
-			unset($this->namesCache[$path]);
329
-		}
330
-		return $result;
331
-	}
332
-
333
-	/**
334
-	 * see https://www.php.net/manual/en/function.rename.php
335
-	 *
336
-	 * @param string $path1
337
-	 * @param string $path2
338
-	 * @return bool
339
-	 */
340
-	public function rename($path1, $path2) {
341
-		// second name always NFC
342
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
343
-	}
344
-
345
-	/**
346
-	 * see https://www.php.net/manual/en/function.copy.php
347
-	 *
348
-	 * @param string $path1
349
-	 * @param string $path2
350
-	 * @return bool
351
-	 */
352
-	public function copy($path1, $path2) {
353
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
354
-	}
355
-
356
-	/**
357
-	 * see https://www.php.net/manual/en/function.fopen.php
358
-	 *
359
-	 * @param string $path
360
-	 * @param string $mode
361
-	 * @return resource|bool
362
-	 */
363
-	public function fopen($path, $mode) {
364
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
365
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
366
-			unset($this->namesCache[$path]);
367
-		}
368
-		return $result;
369
-	}
370
-
371
-	/**
372
-	 * get the mimetype for a file or folder
373
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
374
-	 *
375
-	 * @param string $path
376
-	 * @return string|bool
377
-	 */
378
-	public function getMimeType($path) {
379
-		return $this->storage->getMimeType($this->findPathToUse($path));
380
-	}
381
-
382
-	/**
383
-	 * see https://www.php.net/manual/en/function.hash.php
384
-	 *
385
-	 * @param string $type
386
-	 * @param string $path
387
-	 * @param bool $raw
388
-	 * @return string|bool
389
-	 */
390
-	public function hash($type, $path, $raw = false) {
391
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
392
-	}
393
-
394
-	/**
395
-	 * see https://www.php.net/manual/en/function.free_space.php
396
-	 *
397
-	 * @param string $path
398
-	 * @return int|bool
399
-	 */
400
-	public function free_space($path) {
401
-		return $this->storage->free_space($this->findPathToUse($path));
402
-	}
403
-
404
-	/**
405
-	 * search for occurrences of $query in file names
406
-	 *
407
-	 * @param string $query
408
-	 * @return array|bool
409
-	 */
410
-	public function search($query) {
411
-		return $this->storage->search($query);
412
-	}
413
-
414
-	/**
415
-	 * see https://www.php.net/manual/en/function.touch.php
416
-	 * If the backend does not support the operation, false should be returned
417
-	 *
418
-	 * @param string $path
419
-	 * @param int $mtime
420
-	 * @return bool
421
-	 */
422
-	public function touch($path, $mtime = null) {
423
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
424
-	}
425
-
426
-	/**
427
-	 * get the path to a local version of the file.
428
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
429
-	 *
430
-	 * @param string $path
431
-	 * @return string|bool
432
-	 */
433
-	public function getLocalFile($path) {
434
-		return $this->storage->getLocalFile($this->findPathToUse($path));
435
-	}
436
-
437
-	/**
438
-	 * check if a file or folder has been updated since $time
439
-	 *
440
-	 * @param string $path
441
-	 * @param int $time
442
-	 * @return bool
443
-	 *
444
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
445
-	 * returning true for other changes in the folder is optional
446
-	 */
447
-	public function hasUpdated($path, $time) {
448
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
449
-	}
450
-
451
-	/**
452
-	 * get a cache instance for the storage
453
-	 *
454
-	 * @param string $path
455
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
456
-	 * @return \OC\Files\Cache\Cache
457
-	 */
458
-	public function getCache($path = '', $storage = null) {
459
-		if (!$storage) {
460
-			$storage = $this;
461
-		}
462
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
463
-	}
464
-
465
-	/**
466
-	 * get a scanner instance for the storage
467
-	 *
468
-	 * @param string $path
469
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
470
-	 * @return \OC\Files\Cache\Scanner
471
-	 */
472
-	public function getScanner($path = '', $storage = null) {
473
-		if (!$storage) {
474
-			$storage = $this;
475
-		}
476
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
477
-	}
478
-
479
-	/**
480
-	 * get the ETag for a file or folder
481
-	 *
482
-	 * @param string $path
483
-	 * @return string|bool
484
-	 */
485
-	public function getETag($path) {
486
-		return $this->storage->getETag($this->findPathToUse($path));
487
-	}
488
-
489
-	/**
490
-	 * @param IStorage $sourceStorage
491
-	 * @param string $sourceInternalPath
492
-	 * @param string $targetInternalPath
493
-	 * @return bool
494
-	 */
495
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
496
-		if ($sourceStorage === $this) {
497
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
-		}
499
-
500
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
-		if ($result) {
502
-			unset($this->namesCache[$targetInternalPath]);
503
-		}
504
-		return $result;
505
-	}
506
-
507
-	/**
508
-	 * @param IStorage $sourceStorage
509
-	 * @param string $sourceInternalPath
510
-	 * @param string $targetInternalPath
511
-	 * @return bool
512
-	 */
513
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
514
-		if ($sourceStorage === $this) {
515
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
516
-			if ($result) {
517
-				unset($this->namesCache[$sourceInternalPath]);
518
-				unset($this->namesCache[$targetInternalPath]);
519
-			}
520
-			return $result;
521
-		}
522
-
523
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
524
-		if ($result) {
525
-			unset($this->namesCache[$sourceInternalPath]);
526
-			unset($this->namesCache[$targetInternalPath]);
527
-		}
528
-		return $result;
529
-	}
530
-
531
-	/**
532
-	 * @param string $path
533
-	 * @return array
534
-	 */
535
-	public function getMetaData($path) {
536
-		return $this->storage->getMetaData($this->findPathToUse($path));
537
-	}
538
-
539
-	public function getDirectoryContent($directory): \Traversable {
540
-		return $this->storage->getDirectoryContent($this->findPathToUse($directory));
541
-	}
40
+    /**
41
+     * @var ICache
42
+     */
43
+    private $namesCache;
44
+
45
+    /**
46
+     * @param array $parameters
47
+     */
48
+    public function __construct($parameters) {
49
+        $this->storage = $parameters['storage'];
50
+        $this->namesCache = new CappedMemoryCache();
51
+    }
52
+
53
+    /**
54
+     * Returns whether the given string is only made of ASCII characters
55
+     *
56
+     * @param string $str string
57
+     *
58
+     * @return bool true if the string is all ASCII, false otherwise
59
+     */
60
+    private function isAscii($str) {
61
+        return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
62
+    }
63
+
64
+    /**
65
+     * Checks whether the given path exists in NFC or NFD form after checking
66
+     * each form for each path section and returns the correct form.
67
+     * If no existing path found, returns the path as it was given.
68
+     *
69
+     * @param string $fullPath path to check
70
+     *
71
+     * @return string original or converted path
72
+     */
73
+    private function findPathToUse($fullPath) {
74
+        $cachedPath = $this->namesCache[$fullPath];
75
+        if ($cachedPath !== null) {
76
+            return $cachedPath;
77
+        }
78
+
79
+        $sections = explode('/', $fullPath);
80
+        $path = '';
81
+        foreach ($sections as $section) {
82
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
83
+            if ($convertedPath === null) {
84
+                // no point in continuing if the section was not found, use original path
85
+                return $fullPath;
86
+            }
87
+            $path = $convertedPath . '/';
88
+        }
89
+        $path = rtrim($path, '/');
90
+        return $path;
91
+    }
92
+
93
+    /**
94
+     * Checks whether the last path section of the given path exists in NFC or NFD form
95
+     * and returns the correct form. If no existing path found, returns null.
96
+     *
97
+     * @param string $basePath base path to check
98
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
99
+     *
100
+     * @return string|null original or converted path, or null if none of the forms was found
101
+     */
102
+    private function findPathToUseLastSection($basePath, $lastSection) {
103
+        $fullPath = $basePath . $lastSection;
104
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
105
+            $this->namesCache[$fullPath] = $fullPath;
106
+            return $fullPath;
107
+        }
108
+
109
+        // swap encoding
110
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
111
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
112
+        } else {
113
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
114
+        }
115
+        $otherFullPath = $basePath . $otherFormPath;
116
+        if ($this->storage->file_exists($otherFullPath)) {
117
+            $this->namesCache[$fullPath] = $otherFullPath;
118
+            return $otherFullPath;
119
+        }
120
+
121
+        // return original path, file did not exist at all
122
+        $this->namesCache[$fullPath] = $fullPath;
123
+        return null;
124
+    }
125
+
126
+    /**
127
+     * see https://www.php.net/manual/en/function.mkdir.php
128
+     *
129
+     * @param string $path
130
+     * @return bool
131
+     */
132
+    public function mkdir($path) {
133
+        // note: no conversion here, method should not be called with non-NFC names!
134
+        $result = $this->storage->mkdir($path);
135
+        if ($result) {
136
+            $this->namesCache[$path] = $path;
137
+        }
138
+        return $result;
139
+    }
140
+
141
+    /**
142
+     * see https://www.php.net/manual/en/function.rmdir.php
143
+     *
144
+     * @param string $path
145
+     * @return bool
146
+     */
147
+    public function rmdir($path) {
148
+        $result = $this->storage->rmdir($this->findPathToUse($path));
149
+        if ($result) {
150
+            unset($this->namesCache[$path]);
151
+        }
152
+        return $result;
153
+    }
154
+
155
+    /**
156
+     * see https://www.php.net/manual/en/function.opendir.php
157
+     *
158
+     * @param string $path
159
+     * @return resource|bool
160
+     */
161
+    public function opendir($path) {
162
+        return $this->storage->opendir($this->findPathToUse($path));
163
+    }
164
+
165
+    /**
166
+     * see https://www.php.net/manual/en/function.is_dir.php
167
+     *
168
+     * @param string $path
169
+     * @return bool
170
+     */
171
+    public function is_dir($path) {
172
+        return $this->storage->is_dir($this->findPathToUse($path));
173
+    }
174
+
175
+    /**
176
+     * see https://www.php.net/manual/en/function.is_file.php
177
+     *
178
+     * @param string $path
179
+     * @return bool
180
+     */
181
+    public function is_file($path) {
182
+        return $this->storage->is_file($this->findPathToUse($path));
183
+    }
184
+
185
+    /**
186
+     * see https://www.php.net/manual/en/function.stat.php
187
+     * only the following keys are required in the result: size and mtime
188
+     *
189
+     * @param string $path
190
+     * @return array|bool
191
+     */
192
+    public function stat($path) {
193
+        return $this->storage->stat($this->findPathToUse($path));
194
+    }
195
+
196
+    /**
197
+     * see https://www.php.net/manual/en/function.filetype.php
198
+     *
199
+     * @param string $path
200
+     * @return string|bool
201
+     */
202
+    public function filetype($path) {
203
+        return $this->storage->filetype($this->findPathToUse($path));
204
+    }
205
+
206
+    /**
207
+     * see https://www.php.net/manual/en/function.filesize.php
208
+     * The result for filesize when called on a folder is required to be 0
209
+     *
210
+     * @param string $path
211
+     * @return int|bool
212
+     */
213
+    public function filesize($path) {
214
+        return $this->storage->filesize($this->findPathToUse($path));
215
+    }
216
+
217
+    /**
218
+     * check if a file can be created in $path
219
+     *
220
+     * @param string $path
221
+     * @return bool
222
+     */
223
+    public function isCreatable($path) {
224
+        return $this->storage->isCreatable($this->findPathToUse($path));
225
+    }
226
+
227
+    /**
228
+     * check if a file can be read
229
+     *
230
+     * @param string $path
231
+     * @return bool
232
+     */
233
+    public function isReadable($path) {
234
+        return $this->storage->isReadable($this->findPathToUse($path));
235
+    }
236
+
237
+    /**
238
+     * check if a file can be written to
239
+     *
240
+     * @param string $path
241
+     * @return bool
242
+     */
243
+    public function isUpdatable($path) {
244
+        return $this->storage->isUpdatable($this->findPathToUse($path));
245
+    }
246
+
247
+    /**
248
+     * check if a file can be deleted
249
+     *
250
+     * @param string $path
251
+     * @return bool
252
+     */
253
+    public function isDeletable($path) {
254
+        return $this->storage->isDeletable($this->findPathToUse($path));
255
+    }
256
+
257
+    /**
258
+     * check if a file can be shared
259
+     *
260
+     * @param string $path
261
+     * @return bool
262
+     */
263
+    public function isSharable($path) {
264
+        return $this->storage->isSharable($this->findPathToUse($path));
265
+    }
266
+
267
+    /**
268
+     * get the full permissions of a path.
269
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
270
+     *
271
+     * @param string $path
272
+     * @return int
273
+     */
274
+    public function getPermissions($path) {
275
+        return $this->storage->getPermissions($this->findPathToUse($path));
276
+    }
277
+
278
+    /**
279
+     * see https://www.php.net/manual/en/function.file_exists.php
280
+     *
281
+     * @param string $path
282
+     * @return bool
283
+     */
284
+    public function file_exists($path) {
285
+        return $this->storage->file_exists($this->findPathToUse($path));
286
+    }
287
+
288
+    /**
289
+     * see https://www.php.net/manual/en/function.filemtime.php
290
+     *
291
+     * @param string $path
292
+     * @return int|bool
293
+     */
294
+    public function filemtime($path) {
295
+        return $this->storage->filemtime($this->findPathToUse($path));
296
+    }
297
+
298
+    /**
299
+     * see https://www.php.net/manual/en/function.file_get_contents.php
300
+     *
301
+     * @param string $path
302
+     * @return string|bool
303
+     */
304
+    public function file_get_contents($path) {
305
+        return $this->storage->file_get_contents($this->findPathToUse($path));
306
+    }
307
+
308
+    /**
309
+     * see https://www.php.net/manual/en/function.file_put_contents.php
310
+     *
311
+     * @param string $path
312
+     * @param mixed $data
313
+     * @return int|false
314
+     */
315
+    public function file_put_contents($path, $data) {
316
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
317
+    }
318
+
319
+    /**
320
+     * see https://www.php.net/manual/en/function.unlink.php
321
+     *
322
+     * @param string $path
323
+     * @return bool
324
+     */
325
+    public function unlink($path) {
326
+        $result = $this->storage->unlink($this->findPathToUse($path));
327
+        if ($result) {
328
+            unset($this->namesCache[$path]);
329
+        }
330
+        return $result;
331
+    }
332
+
333
+    /**
334
+     * see https://www.php.net/manual/en/function.rename.php
335
+     *
336
+     * @param string $path1
337
+     * @param string $path2
338
+     * @return bool
339
+     */
340
+    public function rename($path1, $path2) {
341
+        // second name always NFC
342
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
343
+    }
344
+
345
+    /**
346
+     * see https://www.php.net/manual/en/function.copy.php
347
+     *
348
+     * @param string $path1
349
+     * @param string $path2
350
+     * @return bool
351
+     */
352
+    public function copy($path1, $path2) {
353
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
354
+    }
355
+
356
+    /**
357
+     * see https://www.php.net/manual/en/function.fopen.php
358
+     *
359
+     * @param string $path
360
+     * @param string $mode
361
+     * @return resource|bool
362
+     */
363
+    public function fopen($path, $mode) {
364
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
365
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
366
+            unset($this->namesCache[$path]);
367
+        }
368
+        return $result;
369
+    }
370
+
371
+    /**
372
+     * get the mimetype for a file or folder
373
+     * The mimetype for a folder is required to be "httpd/unix-directory"
374
+     *
375
+     * @param string $path
376
+     * @return string|bool
377
+     */
378
+    public function getMimeType($path) {
379
+        return $this->storage->getMimeType($this->findPathToUse($path));
380
+    }
381
+
382
+    /**
383
+     * see https://www.php.net/manual/en/function.hash.php
384
+     *
385
+     * @param string $type
386
+     * @param string $path
387
+     * @param bool $raw
388
+     * @return string|bool
389
+     */
390
+    public function hash($type, $path, $raw = false) {
391
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
392
+    }
393
+
394
+    /**
395
+     * see https://www.php.net/manual/en/function.free_space.php
396
+     *
397
+     * @param string $path
398
+     * @return int|bool
399
+     */
400
+    public function free_space($path) {
401
+        return $this->storage->free_space($this->findPathToUse($path));
402
+    }
403
+
404
+    /**
405
+     * search for occurrences of $query in file names
406
+     *
407
+     * @param string $query
408
+     * @return array|bool
409
+     */
410
+    public function search($query) {
411
+        return $this->storage->search($query);
412
+    }
413
+
414
+    /**
415
+     * see https://www.php.net/manual/en/function.touch.php
416
+     * If the backend does not support the operation, false should be returned
417
+     *
418
+     * @param string $path
419
+     * @param int $mtime
420
+     * @return bool
421
+     */
422
+    public function touch($path, $mtime = null) {
423
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
424
+    }
425
+
426
+    /**
427
+     * get the path to a local version of the file.
428
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
429
+     *
430
+     * @param string $path
431
+     * @return string|bool
432
+     */
433
+    public function getLocalFile($path) {
434
+        return $this->storage->getLocalFile($this->findPathToUse($path));
435
+    }
436
+
437
+    /**
438
+     * check if a file or folder has been updated since $time
439
+     *
440
+     * @param string $path
441
+     * @param int $time
442
+     * @return bool
443
+     *
444
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
445
+     * returning true for other changes in the folder is optional
446
+     */
447
+    public function hasUpdated($path, $time) {
448
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
449
+    }
450
+
451
+    /**
452
+     * get a cache instance for the storage
453
+     *
454
+     * @param string $path
455
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
456
+     * @return \OC\Files\Cache\Cache
457
+     */
458
+    public function getCache($path = '', $storage = null) {
459
+        if (!$storage) {
460
+            $storage = $this;
461
+        }
462
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
463
+    }
464
+
465
+    /**
466
+     * get a scanner instance for the storage
467
+     *
468
+     * @param string $path
469
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
470
+     * @return \OC\Files\Cache\Scanner
471
+     */
472
+    public function getScanner($path = '', $storage = null) {
473
+        if (!$storage) {
474
+            $storage = $this;
475
+        }
476
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
477
+    }
478
+
479
+    /**
480
+     * get the ETag for a file or folder
481
+     *
482
+     * @param string $path
483
+     * @return string|bool
484
+     */
485
+    public function getETag($path) {
486
+        return $this->storage->getETag($this->findPathToUse($path));
487
+    }
488
+
489
+    /**
490
+     * @param IStorage $sourceStorage
491
+     * @param string $sourceInternalPath
492
+     * @param string $targetInternalPath
493
+     * @return bool
494
+     */
495
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
496
+        if ($sourceStorage === $this) {
497
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
+        }
499
+
500
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
+        if ($result) {
502
+            unset($this->namesCache[$targetInternalPath]);
503
+        }
504
+        return $result;
505
+    }
506
+
507
+    /**
508
+     * @param IStorage $sourceStorage
509
+     * @param string $sourceInternalPath
510
+     * @param string $targetInternalPath
511
+     * @return bool
512
+     */
513
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
514
+        if ($sourceStorage === $this) {
515
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
516
+            if ($result) {
517
+                unset($this->namesCache[$sourceInternalPath]);
518
+                unset($this->namesCache[$targetInternalPath]);
519
+            }
520
+            return $result;
521
+        }
522
+
523
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
524
+        if ($result) {
525
+            unset($this->namesCache[$sourceInternalPath]);
526
+            unset($this->namesCache[$targetInternalPath]);
527
+        }
528
+        return $result;
529
+    }
530
+
531
+    /**
532
+     * @param string $path
533
+     * @return array
534
+     */
535
+    public function getMetaData($path) {
536
+        return $this->storage->getMetaData($this->findPathToUse($path));
537
+    }
538
+
539
+    public function getDirectoryContent($directory): \Traversable {
540
+        return $this->storage->getDirectoryContent($this->findPathToUse($directory));
541
+    }
542 542
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Wrapper.php 1 patch
Indentation   +604 added lines, -604 removed lines patch added patch discarded remove patch
@@ -37,608 +37,608 @@
 block discarded – undo
37 37
 use OCP\Lock\ILockingProvider;
38 38
 
39 39
 class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
40
-	/**
41
-	 * @var \OC\Files\Storage\Storage $storage
42
-	 */
43
-	protected $storage;
44
-
45
-	public $cache;
46
-	public $scanner;
47
-	public $watcher;
48
-	public $propagator;
49
-	public $updater;
50
-
51
-	/**
52
-	 * @param array $parameters
53
-	 */
54
-	public function __construct($parameters) {
55
-		$this->storage = $parameters['storage'];
56
-	}
57
-
58
-	/**
59
-	 * @return \OC\Files\Storage\Storage
60
-	 */
61
-	public function getWrapperStorage() {
62
-		return $this->storage;
63
-	}
64
-
65
-	/**
66
-	 * Get the identifier for the storage,
67
-	 * the returned id should be the same for every storage object that is created with the same parameters
68
-	 * and two storage objects with the same id should refer to two storages that display the same files.
69
-	 *
70
-	 * @return string
71
-	 */
72
-	public function getId() {
73
-		return $this->getWrapperStorage()->getId();
74
-	}
75
-
76
-	/**
77
-	 * see https://www.php.net/manual/en/function.mkdir.php
78
-	 *
79
-	 * @param string $path
80
-	 * @return bool
81
-	 */
82
-	public function mkdir($path) {
83
-		return $this->getWrapperStorage()->mkdir($path);
84
-	}
85
-
86
-	/**
87
-	 * see https://www.php.net/manual/en/function.rmdir.php
88
-	 *
89
-	 * @param string $path
90
-	 * @return bool
91
-	 */
92
-	public function rmdir($path) {
93
-		return $this->getWrapperStorage()->rmdir($path);
94
-	}
95
-
96
-	/**
97
-	 * see https://www.php.net/manual/en/function.opendir.php
98
-	 *
99
-	 * @param string $path
100
-	 * @return resource|bool
101
-	 */
102
-	public function opendir($path) {
103
-		return $this->getWrapperStorage()->opendir($path);
104
-	}
105
-
106
-	/**
107
-	 * see https://www.php.net/manual/en/function.is_dir.php
108
-	 *
109
-	 * @param string $path
110
-	 * @return bool
111
-	 */
112
-	public function is_dir($path) {
113
-		return $this->getWrapperStorage()->is_dir($path);
114
-	}
115
-
116
-	/**
117
-	 * see https://www.php.net/manual/en/function.is_file.php
118
-	 *
119
-	 * @param string $path
120
-	 * @return bool
121
-	 */
122
-	public function is_file($path) {
123
-		return $this->getWrapperStorage()->is_file($path);
124
-	}
125
-
126
-	/**
127
-	 * see https://www.php.net/manual/en/function.stat.php
128
-	 * only the following keys are required in the result: size and mtime
129
-	 *
130
-	 * @param string $path
131
-	 * @return array|bool
132
-	 */
133
-	public function stat($path) {
134
-		return $this->getWrapperStorage()->stat($path);
135
-	}
136
-
137
-	/**
138
-	 * see https://www.php.net/manual/en/function.filetype.php
139
-	 *
140
-	 * @param string $path
141
-	 * @return string|bool
142
-	 */
143
-	public function filetype($path) {
144
-		return $this->getWrapperStorage()->filetype($path);
145
-	}
146
-
147
-	/**
148
-	 * see https://www.php.net/manual/en/function.filesize.php
149
-	 * The result for filesize when called on a folder is required to be 0
150
-	 *
151
-	 * @param string $path
152
-	 * @return int|bool
153
-	 */
154
-	public function filesize($path) {
155
-		return $this->getWrapperStorage()->filesize($path);
156
-	}
157
-
158
-	/**
159
-	 * check if a file can be created in $path
160
-	 *
161
-	 * @param string $path
162
-	 * @return bool
163
-	 */
164
-	public function isCreatable($path) {
165
-		return $this->getWrapperStorage()->isCreatable($path);
166
-	}
167
-
168
-	/**
169
-	 * check if a file can be read
170
-	 *
171
-	 * @param string $path
172
-	 * @return bool
173
-	 */
174
-	public function isReadable($path) {
175
-		return $this->getWrapperStorage()->isReadable($path);
176
-	}
177
-
178
-	/**
179
-	 * check if a file can be written to
180
-	 *
181
-	 * @param string $path
182
-	 * @return bool
183
-	 */
184
-	public function isUpdatable($path) {
185
-		return $this->getWrapperStorage()->isUpdatable($path);
186
-	}
187
-
188
-	/**
189
-	 * check if a file can be deleted
190
-	 *
191
-	 * @param string $path
192
-	 * @return bool
193
-	 */
194
-	public function isDeletable($path) {
195
-		return $this->getWrapperStorage()->isDeletable($path);
196
-	}
197
-
198
-	/**
199
-	 * check if a file can be shared
200
-	 *
201
-	 * @param string $path
202
-	 * @return bool
203
-	 */
204
-	public function isSharable($path) {
205
-		return $this->getWrapperStorage()->isSharable($path);
206
-	}
207
-
208
-	/**
209
-	 * get the full permissions of a path.
210
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
211
-	 *
212
-	 * @param string $path
213
-	 * @return int
214
-	 */
215
-	public function getPermissions($path) {
216
-		return $this->getWrapperStorage()->getPermissions($path);
217
-	}
218
-
219
-	/**
220
-	 * see https://www.php.net/manual/en/function.file_exists.php
221
-	 *
222
-	 * @param string $path
223
-	 * @return bool
224
-	 */
225
-	public function file_exists($path) {
226
-		return $this->getWrapperStorage()->file_exists($path);
227
-	}
228
-
229
-	/**
230
-	 * see https://www.php.net/manual/en/function.filemtime.php
231
-	 *
232
-	 * @param string $path
233
-	 * @return int|bool
234
-	 */
235
-	public function filemtime($path) {
236
-		return $this->getWrapperStorage()->filemtime($path);
237
-	}
238
-
239
-	/**
240
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
241
-	 *
242
-	 * @param string $path
243
-	 * @return string|bool
244
-	 */
245
-	public function file_get_contents($path) {
246
-		return $this->getWrapperStorage()->file_get_contents($path);
247
-	}
248
-
249
-	/**
250
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
251
-	 *
252
-	 * @param string $path
253
-	 * @param mixed $data
254
-	 * @return int|false
255
-	 */
256
-	public function file_put_contents($path, $data) {
257
-		return $this->getWrapperStorage()->file_put_contents($path, $data);
258
-	}
259
-
260
-	/**
261
-	 * see https://www.php.net/manual/en/function.unlink.php
262
-	 *
263
-	 * @param string $path
264
-	 * @return bool
265
-	 */
266
-	public function unlink($path) {
267
-		return $this->getWrapperStorage()->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
-		return $this->getWrapperStorage()->rename($path1, $path2);
279
-	}
280
-
281
-	/**
282
-	 * see https://www.php.net/manual/en/function.copy.php
283
-	 *
284
-	 * @param string $path1
285
-	 * @param string $path2
286
-	 * @return bool
287
-	 */
288
-	public function copy($path1, $path2) {
289
-		return $this->getWrapperStorage()->copy($path1, $path2);
290
-	}
291
-
292
-	/**
293
-	 * see https://www.php.net/manual/en/function.fopen.php
294
-	 *
295
-	 * @param string $path
296
-	 * @param string $mode
297
-	 * @return resource|bool
298
-	 */
299
-	public function fopen($path, $mode) {
300
-		return $this->getWrapperStorage()->fopen($path, $mode);
301
-	}
302
-
303
-	/**
304
-	 * get the mimetype for a file or folder
305
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
306
-	 *
307
-	 * @param string $path
308
-	 * @return string|bool
309
-	 */
310
-	public function getMimeType($path) {
311
-		return $this->getWrapperStorage()->getMimeType($path);
312
-	}
313
-
314
-	/**
315
-	 * see https://www.php.net/manual/en/function.hash.php
316
-	 *
317
-	 * @param string $type
318
-	 * @param string $path
319
-	 * @param bool $raw
320
-	 * @return string|bool
321
-	 */
322
-	public function hash($type, $path, $raw = false) {
323
-		return $this->getWrapperStorage()->hash($type, $path, $raw);
324
-	}
325
-
326
-	/**
327
-	 * see https://www.php.net/manual/en/function.free_space.php
328
-	 *
329
-	 * @param string $path
330
-	 * @return int|bool
331
-	 */
332
-	public function free_space($path) {
333
-		return $this->getWrapperStorage()->free_space($path);
334
-	}
335
-
336
-	/**
337
-	 * search for occurrences of $query in file names
338
-	 *
339
-	 * @param string $query
340
-	 * @return array|bool
341
-	 */
342
-	public function search($query) {
343
-		return $this->getWrapperStorage()->search($query);
344
-	}
345
-
346
-	/**
347
-	 * see https://www.php.net/manual/en/function.touch.php
348
-	 * If the backend does not support the operation, false should be returned
349
-	 *
350
-	 * @param string $path
351
-	 * @param int $mtime
352
-	 * @return bool
353
-	 */
354
-	public function touch($path, $mtime = null) {
355
-		return $this->getWrapperStorage()->touch($path, $mtime);
356
-	}
357
-
358
-	/**
359
-	 * get the path to a local version of the file.
360
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
361
-	 *
362
-	 * @param string $path
363
-	 * @return string|bool
364
-	 */
365
-	public function getLocalFile($path) {
366
-		return $this->getWrapperStorage()->getLocalFile($path);
367
-	}
368
-
369
-	/**
370
-	 * check if a file or folder has been updated since $time
371
-	 *
372
-	 * @param string $path
373
-	 * @param int $time
374
-	 * @return bool
375
-	 *
376
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
377
-	 * returning true for other changes in the folder is optional
378
-	 */
379
-	public function hasUpdated($path, $time) {
380
-		return $this->getWrapperStorage()->hasUpdated($path, $time);
381
-	}
382
-
383
-	/**
384
-	 * get a cache instance for the storage
385
-	 *
386
-	 * @param string $path
387
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
388
-	 * @return \OC\Files\Cache\Cache
389
-	 */
390
-	public function getCache($path = '', $storage = null) {
391
-		if (!$storage) {
392
-			$storage = $this;
393
-		}
394
-		return $this->getWrapperStorage()->getCache($path, $storage);
395
-	}
396
-
397
-	/**
398
-	 * get a scanner instance for the storage
399
-	 *
400
-	 * @param string $path
401
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
402
-	 * @return \OC\Files\Cache\Scanner
403
-	 */
404
-	public function getScanner($path = '', $storage = null) {
405
-		if (!$storage) {
406
-			$storage = $this;
407
-		}
408
-		return $this->getWrapperStorage()->getScanner($path, $storage);
409
-	}
410
-
411
-
412
-	/**
413
-	 * get the user id of the owner of a file or folder
414
-	 *
415
-	 * @param string $path
416
-	 * @return string
417
-	 */
418
-	public function getOwner($path) {
419
-		return $this->getWrapperStorage()->getOwner($path);
420
-	}
421
-
422
-	/**
423
-	 * get a watcher instance for the cache
424
-	 *
425
-	 * @param string $path
426
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
427
-	 * @return \OC\Files\Cache\Watcher
428
-	 */
429
-	public function getWatcher($path = '', $storage = null) {
430
-		if (!$storage) {
431
-			$storage = $this;
432
-		}
433
-		return $this->getWrapperStorage()->getWatcher($path, $storage);
434
-	}
435
-
436
-	public function getPropagator($storage = null) {
437
-		if (!$storage) {
438
-			$storage = $this;
439
-		}
440
-		return $this->getWrapperStorage()->getPropagator($storage);
441
-	}
442
-
443
-	public function getUpdater($storage = null) {
444
-		if (!$storage) {
445
-			$storage = $this;
446
-		}
447
-		return $this->getWrapperStorage()->getUpdater($storage);
448
-	}
449
-
450
-	/**
451
-	 * @return \OC\Files\Cache\Storage
452
-	 */
453
-	public function getStorageCache() {
454
-		return $this->getWrapperStorage()->getStorageCache();
455
-	}
456
-
457
-	/**
458
-	 * get the ETag for a file or folder
459
-	 *
460
-	 * @param string $path
461
-	 * @return string|bool
462
-	 */
463
-	public function getETag($path) {
464
-		return $this->getWrapperStorage()->getETag($path);
465
-	}
466
-
467
-	/**
468
-	 * Returns true
469
-	 *
470
-	 * @return true
471
-	 */
472
-	public function test() {
473
-		return $this->getWrapperStorage()->test();
474
-	}
475
-
476
-	/**
477
-	 * Returns the wrapped storage's value for isLocal()
478
-	 *
479
-	 * @return bool wrapped storage's isLocal() value
480
-	 */
481
-	public function isLocal() {
482
-		return $this->getWrapperStorage()->isLocal();
483
-	}
484
-
485
-	/**
486
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
487
-	 *
488
-	 * @param string $class
489
-	 * @return bool
490
-	 */
491
-	public function instanceOfStorage($class) {
492
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
493
-			// FIXME Temporary fix to keep existing checks working
494
-			$class = '\OCA\Files_Sharing\SharedStorage';
495
-		}
496
-		return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
497
-	}
498
-
499
-	/**
500
-	 * Pass any methods custom to specific storage implementations to the wrapped storage
501
-	 *
502
-	 * @param string $method
503
-	 * @param array $args
504
-	 * @return mixed
505
-	 */
506
-	public function __call($method, $args) {
507
-		return call_user_func_array([$this->getWrapperStorage(), $method], $args);
508
-	}
509
-
510
-	/**
511
-	 * A custom storage implementation can return an url for direct download of a give file.
512
-	 *
513
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
514
-	 *
515
-	 * @param string $path
516
-	 * @return array|bool
517
-	 */
518
-	public function getDirectDownload($path) {
519
-		return $this->getWrapperStorage()->getDirectDownload($path);
520
-	}
521
-
522
-	/**
523
-	 * Get availability of the storage
524
-	 *
525
-	 * @return array [ available, last_checked ]
526
-	 */
527
-	public function getAvailability() {
528
-		return $this->getWrapperStorage()->getAvailability();
529
-	}
530
-
531
-	/**
532
-	 * Set availability of the storage
533
-	 *
534
-	 * @param bool $isAvailable
535
-	 */
536
-	public function setAvailability($isAvailable) {
537
-		$this->getWrapperStorage()->setAvailability($isAvailable);
538
-	}
539
-
540
-	/**
541
-	 * @param string $path the path of the target folder
542
-	 * @param string $fileName the name of the file itself
543
-	 * @return void
544
-	 * @throws InvalidPathException
545
-	 */
546
-	public function verifyPath($path, $fileName) {
547
-		$this->getWrapperStorage()->verifyPath($path, $fileName);
548
-	}
549
-
550
-	/**
551
-	 * @param IStorage $sourceStorage
552
-	 * @param string $sourceInternalPath
553
-	 * @param string $targetInternalPath
554
-	 * @return bool
555
-	 */
556
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
557
-		if ($sourceStorage === $this) {
558
-			return $this->copy($sourceInternalPath, $targetInternalPath);
559
-		}
560
-
561
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
562
-	}
563
-
564
-	/**
565
-	 * @param IStorage $sourceStorage
566
-	 * @param string $sourceInternalPath
567
-	 * @param string $targetInternalPath
568
-	 * @return bool
569
-	 */
570
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
571
-		if ($sourceStorage === $this) {
572
-			return $this->rename($sourceInternalPath, $targetInternalPath);
573
-		}
574
-
575
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
576
-	}
577
-
578
-	/**
579
-	 * @param string $path
580
-	 * @return array
581
-	 */
582
-	public function getMetaData($path) {
583
-		return $this->getWrapperStorage()->getMetaData($path);
584
-	}
585
-
586
-	/**
587
-	 * @param string $path
588
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
589
-	 * @param \OCP\Lock\ILockingProvider $provider
590
-	 * @throws \OCP\Lock\LockedException
591
-	 */
592
-	public function acquireLock($path, $type, ILockingProvider $provider) {
593
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
594
-			$this->getWrapperStorage()->acquireLock($path, $type, $provider);
595
-		}
596
-	}
597
-
598
-	/**
599
-	 * @param string $path
600
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
601
-	 * @param \OCP\Lock\ILockingProvider $provider
602
-	 */
603
-	public function releaseLock($path, $type, ILockingProvider $provider) {
604
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
605
-			$this->getWrapperStorage()->releaseLock($path, $type, $provider);
606
-		}
607
-	}
608
-
609
-	/**
610
-	 * @param string $path
611
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
612
-	 * @param \OCP\Lock\ILockingProvider $provider
613
-	 */
614
-	public function changeLock($path, $type, ILockingProvider $provider) {
615
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
616
-			$this->getWrapperStorage()->changeLock($path, $type, $provider);
617
-		}
618
-	}
619
-
620
-	/**
621
-	 * @return bool
622
-	 */
623
-	public function needsPartFile() {
624
-		return $this->getWrapperStorage()->needsPartFile();
625
-	}
626
-
627
-	public function writeStream(string $path, $stream, int $size = null): int {
628
-		$storage = $this->getWrapperStorage();
629
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
630
-			/** @var IWriteStreamStorage $storage */
631
-			return $storage->writeStream($path, $stream, $size);
632
-		} else {
633
-			$target = $this->fopen($path, 'w');
634
-			list($count, $result) = \OC_Helper::streamCopy($stream, $target);
635
-			fclose($stream);
636
-			fclose($target);
637
-			return $count;
638
-		}
639
-	}
640
-
641
-	public function getDirectoryContent($directory): \Traversable {
642
-		return $this->getWrapperStorage()->getDirectoryContent($directory);
643
-	}
40
+    /**
41
+     * @var \OC\Files\Storage\Storage $storage
42
+     */
43
+    protected $storage;
44
+
45
+    public $cache;
46
+    public $scanner;
47
+    public $watcher;
48
+    public $propagator;
49
+    public $updater;
50
+
51
+    /**
52
+     * @param array $parameters
53
+     */
54
+    public function __construct($parameters) {
55
+        $this->storage = $parameters['storage'];
56
+    }
57
+
58
+    /**
59
+     * @return \OC\Files\Storage\Storage
60
+     */
61
+    public function getWrapperStorage() {
62
+        return $this->storage;
63
+    }
64
+
65
+    /**
66
+     * Get the identifier for the storage,
67
+     * the returned id should be the same for every storage object that is created with the same parameters
68
+     * and two storage objects with the same id should refer to two storages that display the same files.
69
+     *
70
+     * @return string
71
+     */
72
+    public function getId() {
73
+        return $this->getWrapperStorage()->getId();
74
+    }
75
+
76
+    /**
77
+     * see https://www.php.net/manual/en/function.mkdir.php
78
+     *
79
+     * @param string $path
80
+     * @return bool
81
+     */
82
+    public function mkdir($path) {
83
+        return $this->getWrapperStorage()->mkdir($path);
84
+    }
85
+
86
+    /**
87
+     * see https://www.php.net/manual/en/function.rmdir.php
88
+     *
89
+     * @param string $path
90
+     * @return bool
91
+     */
92
+    public function rmdir($path) {
93
+        return $this->getWrapperStorage()->rmdir($path);
94
+    }
95
+
96
+    /**
97
+     * see https://www.php.net/manual/en/function.opendir.php
98
+     *
99
+     * @param string $path
100
+     * @return resource|bool
101
+     */
102
+    public function opendir($path) {
103
+        return $this->getWrapperStorage()->opendir($path);
104
+    }
105
+
106
+    /**
107
+     * see https://www.php.net/manual/en/function.is_dir.php
108
+     *
109
+     * @param string $path
110
+     * @return bool
111
+     */
112
+    public function is_dir($path) {
113
+        return $this->getWrapperStorage()->is_dir($path);
114
+    }
115
+
116
+    /**
117
+     * see https://www.php.net/manual/en/function.is_file.php
118
+     *
119
+     * @param string $path
120
+     * @return bool
121
+     */
122
+    public function is_file($path) {
123
+        return $this->getWrapperStorage()->is_file($path);
124
+    }
125
+
126
+    /**
127
+     * see https://www.php.net/manual/en/function.stat.php
128
+     * only the following keys are required in the result: size and mtime
129
+     *
130
+     * @param string $path
131
+     * @return array|bool
132
+     */
133
+    public function stat($path) {
134
+        return $this->getWrapperStorage()->stat($path);
135
+    }
136
+
137
+    /**
138
+     * see https://www.php.net/manual/en/function.filetype.php
139
+     *
140
+     * @param string $path
141
+     * @return string|bool
142
+     */
143
+    public function filetype($path) {
144
+        return $this->getWrapperStorage()->filetype($path);
145
+    }
146
+
147
+    /**
148
+     * see https://www.php.net/manual/en/function.filesize.php
149
+     * The result for filesize when called on a folder is required to be 0
150
+     *
151
+     * @param string $path
152
+     * @return int|bool
153
+     */
154
+    public function filesize($path) {
155
+        return $this->getWrapperStorage()->filesize($path);
156
+    }
157
+
158
+    /**
159
+     * check if a file can be created in $path
160
+     *
161
+     * @param string $path
162
+     * @return bool
163
+     */
164
+    public function isCreatable($path) {
165
+        return $this->getWrapperStorage()->isCreatable($path);
166
+    }
167
+
168
+    /**
169
+     * check if a file can be read
170
+     *
171
+     * @param string $path
172
+     * @return bool
173
+     */
174
+    public function isReadable($path) {
175
+        return $this->getWrapperStorage()->isReadable($path);
176
+    }
177
+
178
+    /**
179
+     * check if a file can be written to
180
+     *
181
+     * @param string $path
182
+     * @return bool
183
+     */
184
+    public function isUpdatable($path) {
185
+        return $this->getWrapperStorage()->isUpdatable($path);
186
+    }
187
+
188
+    /**
189
+     * check if a file can be deleted
190
+     *
191
+     * @param string $path
192
+     * @return bool
193
+     */
194
+    public function isDeletable($path) {
195
+        return $this->getWrapperStorage()->isDeletable($path);
196
+    }
197
+
198
+    /**
199
+     * check if a file can be shared
200
+     *
201
+     * @param string $path
202
+     * @return bool
203
+     */
204
+    public function isSharable($path) {
205
+        return $this->getWrapperStorage()->isSharable($path);
206
+    }
207
+
208
+    /**
209
+     * get the full permissions of a path.
210
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
211
+     *
212
+     * @param string $path
213
+     * @return int
214
+     */
215
+    public function getPermissions($path) {
216
+        return $this->getWrapperStorage()->getPermissions($path);
217
+    }
218
+
219
+    /**
220
+     * see https://www.php.net/manual/en/function.file_exists.php
221
+     *
222
+     * @param string $path
223
+     * @return bool
224
+     */
225
+    public function file_exists($path) {
226
+        return $this->getWrapperStorage()->file_exists($path);
227
+    }
228
+
229
+    /**
230
+     * see https://www.php.net/manual/en/function.filemtime.php
231
+     *
232
+     * @param string $path
233
+     * @return int|bool
234
+     */
235
+    public function filemtime($path) {
236
+        return $this->getWrapperStorage()->filemtime($path);
237
+    }
238
+
239
+    /**
240
+     * see https://www.php.net/manual/en/function.file_get_contents.php
241
+     *
242
+     * @param string $path
243
+     * @return string|bool
244
+     */
245
+    public function file_get_contents($path) {
246
+        return $this->getWrapperStorage()->file_get_contents($path);
247
+    }
248
+
249
+    /**
250
+     * see https://www.php.net/manual/en/function.file_put_contents.php
251
+     *
252
+     * @param string $path
253
+     * @param mixed $data
254
+     * @return int|false
255
+     */
256
+    public function file_put_contents($path, $data) {
257
+        return $this->getWrapperStorage()->file_put_contents($path, $data);
258
+    }
259
+
260
+    /**
261
+     * see https://www.php.net/manual/en/function.unlink.php
262
+     *
263
+     * @param string $path
264
+     * @return bool
265
+     */
266
+    public function unlink($path) {
267
+        return $this->getWrapperStorage()->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
+        return $this->getWrapperStorage()->rename($path1, $path2);
279
+    }
280
+
281
+    /**
282
+     * see https://www.php.net/manual/en/function.copy.php
283
+     *
284
+     * @param string $path1
285
+     * @param string $path2
286
+     * @return bool
287
+     */
288
+    public function copy($path1, $path2) {
289
+        return $this->getWrapperStorage()->copy($path1, $path2);
290
+    }
291
+
292
+    /**
293
+     * see https://www.php.net/manual/en/function.fopen.php
294
+     *
295
+     * @param string $path
296
+     * @param string $mode
297
+     * @return resource|bool
298
+     */
299
+    public function fopen($path, $mode) {
300
+        return $this->getWrapperStorage()->fopen($path, $mode);
301
+    }
302
+
303
+    /**
304
+     * get the mimetype for a file or folder
305
+     * The mimetype for a folder is required to be "httpd/unix-directory"
306
+     *
307
+     * @param string $path
308
+     * @return string|bool
309
+     */
310
+    public function getMimeType($path) {
311
+        return $this->getWrapperStorage()->getMimeType($path);
312
+    }
313
+
314
+    /**
315
+     * see https://www.php.net/manual/en/function.hash.php
316
+     *
317
+     * @param string $type
318
+     * @param string $path
319
+     * @param bool $raw
320
+     * @return string|bool
321
+     */
322
+    public function hash($type, $path, $raw = false) {
323
+        return $this->getWrapperStorage()->hash($type, $path, $raw);
324
+    }
325
+
326
+    /**
327
+     * see https://www.php.net/manual/en/function.free_space.php
328
+     *
329
+     * @param string $path
330
+     * @return int|bool
331
+     */
332
+    public function free_space($path) {
333
+        return $this->getWrapperStorage()->free_space($path);
334
+    }
335
+
336
+    /**
337
+     * search for occurrences of $query in file names
338
+     *
339
+     * @param string $query
340
+     * @return array|bool
341
+     */
342
+    public function search($query) {
343
+        return $this->getWrapperStorage()->search($query);
344
+    }
345
+
346
+    /**
347
+     * see https://www.php.net/manual/en/function.touch.php
348
+     * If the backend does not support the operation, false should be returned
349
+     *
350
+     * @param string $path
351
+     * @param int $mtime
352
+     * @return bool
353
+     */
354
+    public function touch($path, $mtime = null) {
355
+        return $this->getWrapperStorage()->touch($path, $mtime);
356
+    }
357
+
358
+    /**
359
+     * get the path to a local version of the file.
360
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
361
+     *
362
+     * @param string $path
363
+     * @return string|bool
364
+     */
365
+    public function getLocalFile($path) {
366
+        return $this->getWrapperStorage()->getLocalFile($path);
367
+    }
368
+
369
+    /**
370
+     * check if a file or folder has been updated since $time
371
+     *
372
+     * @param string $path
373
+     * @param int $time
374
+     * @return bool
375
+     *
376
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
377
+     * returning true for other changes in the folder is optional
378
+     */
379
+    public function hasUpdated($path, $time) {
380
+        return $this->getWrapperStorage()->hasUpdated($path, $time);
381
+    }
382
+
383
+    /**
384
+     * get a cache instance for the storage
385
+     *
386
+     * @param string $path
387
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
388
+     * @return \OC\Files\Cache\Cache
389
+     */
390
+    public function getCache($path = '', $storage = null) {
391
+        if (!$storage) {
392
+            $storage = $this;
393
+        }
394
+        return $this->getWrapperStorage()->getCache($path, $storage);
395
+    }
396
+
397
+    /**
398
+     * get a scanner instance for the storage
399
+     *
400
+     * @param string $path
401
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
402
+     * @return \OC\Files\Cache\Scanner
403
+     */
404
+    public function getScanner($path = '', $storage = null) {
405
+        if (!$storage) {
406
+            $storage = $this;
407
+        }
408
+        return $this->getWrapperStorage()->getScanner($path, $storage);
409
+    }
410
+
411
+
412
+    /**
413
+     * get the user id of the owner of a file or folder
414
+     *
415
+     * @param string $path
416
+     * @return string
417
+     */
418
+    public function getOwner($path) {
419
+        return $this->getWrapperStorage()->getOwner($path);
420
+    }
421
+
422
+    /**
423
+     * get a watcher instance for the cache
424
+     *
425
+     * @param string $path
426
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
427
+     * @return \OC\Files\Cache\Watcher
428
+     */
429
+    public function getWatcher($path = '', $storage = null) {
430
+        if (!$storage) {
431
+            $storage = $this;
432
+        }
433
+        return $this->getWrapperStorage()->getWatcher($path, $storage);
434
+    }
435
+
436
+    public function getPropagator($storage = null) {
437
+        if (!$storage) {
438
+            $storage = $this;
439
+        }
440
+        return $this->getWrapperStorage()->getPropagator($storage);
441
+    }
442
+
443
+    public function getUpdater($storage = null) {
444
+        if (!$storage) {
445
+            $storage = $this;
446
+        }
447
+        return $this->getWrapperStorage()->getUpdater($storage);
448
+    }
449
+
450
+    /**
451
+     * @return \OC\Files\Cache\Storage
452
+     */
453
+    public function getStorageCache() {
454
+        return $this->getWrapperStorage()->getStorageCache();
455
+    }
456
+
457
+    /**
458
+     * get the ETag for a file or folder
459
+     *
460
+     * @param string $path
461
+     * @return string|bool
462
+     */
463
+    public function getETag($path) {
464
+        return $this->getWrapperStorage()->getETag($path);
465
+    }
466
+
467
+    /**
468
+     * Returns true
469
+     *
470
+     * @return true
471
+     */
472
+    public function test() {
473
+        return $this->getWrapperStorage()->test();
474
+    }
475
+
476
+    /**
477
+     * Returns the wrapped storage's value for isLocal()
478
+     *
479
+     * @return bool wrapped storage's isLocal() value
480
+     */
481
+    public function isLocal() {
482
+        return $this->getWrapperStorage()->isLocal();
483
+    }
484
+
485
+    /**
486
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
487
+     *
488
+     * @param string $class
489
+     * @return bool
490
+     */
491
+    public function instanceOfStorage($class) {
492
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
493
+            // FIXME Temporary fix to keep existing checks working
494
+            $class = '\OCA\Files_Sharing\SharedStorage';
495
+        }
496
+        return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
497
+    }
498
+
499
+    /**
500
+     * Pass any methods custom to specific storage implementations to the wrapped storage
501
+     *
502
+     * @param string $method
503
+     * @param array $args
504
+     * @return mixed
505
+     */
506
+    public function __call($method, $args) {
507
+        return call_user_func_array([$this->getWrapperStorage(), $method], $args);
508
+    }
509
+
510
+    /**
511
+     * A custom storage implementation can return an url for direct download of a give file.
512
+     *
513
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
514
+     *
515
+     * @param string $path
516
+     * @return array|bool
517
+     */
518
+    public function getDirectDownload($path) {
519
+        return $this->getWrapperStorage()->getDirectDownload($path);
520
+    }
521
+
522
+    /**
523
+     * Get availability of the storage
524
+     *
525
+     * @return array [ available, last_checked ]
526
+     */
527
+    public function getAvailability() {
528
+        return $this->getWrapperStorage()->getAvailability();
529
+    }
530
+
531
+    /**
532
+     * Set availability of the storage
533
+     *
534
+     * @param bool $isAvailable
535
+     */
536
+    public function setAvailability($isAvailable) {
537
+        $this->getWrapperStorage()->setAvailability($isAvailable);
538
+    }
539
+
540
+    /**
541
+     * @param string $path the path of the target folder
542
+     * @param string $fileName the name of the file itself
543
+     * @return void
544
+     * @throws InvalidPathException
545
+     */
546
+    public function verifyPath($path, $fileName) {
547
+        $this->getWrapperStorage()->verifyPath($path, $fileName);
548
+    }
549
+
550
+    /**
551
+     * @param IStorage $sourceStorage
552
+     * @param string $sourceInternalPath
553
+     * @param string $targetInternalPath
554
+     * @return bool
555
+     */
556
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
557
+        if ($sourceStorage === $this) {
558
+            return $this->copy($sourceInternalPath, $targetInternalPath);
559
+        }
560
+
561
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
562
+    }
563
+
564
+    /**
565
+     * @param IStorage $sourceStorage
566
+     * @param string $sourceInternalPath
567
+     * @param string $targetInternalPath
568
+     * @return bool
569
+     */
570
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
571
+        if ($sourceStorage === $this) {
572
+            return $this->rename($sourceInternalPath, $targetInternalPath);
573
+        }
574
+
575
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
576
+    }
577
+
578
+    /**
579
+     * @param string $path
580
+     * @return array
581
+     */
582
+    public function getMetaData($path) {
583
+        return $this->getWrapperStorage()->getMetaData($path);
584
+    }
585
+
586
+    /**
587
+     * @param string $path
588
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
589
+     * @param \OCP\Lock\ILockingProvider $provider
590
+     * @throws \OCP\Lock\LockedException
591
+     */
592
+    public function acquireLock($path, $type, ILockingProvider $provider) {
593
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
594
+            $this->getWrapperStorage()->acquireLock($path, $type, $provider);
595
+        }
596
+    }
597
+
598
+    /**
599
+     * @param string $path
600
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
601
+     * @param \OCP\Lock\ILockingProvider $provider
602
+     */
603
+    public function releaseLock($path, $type, ILockingProvider $provider) {
604
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
605
+            $this->getWrapperStorage()->releaseLock($path, $type, $provider);
606
+        }
607
+    }
608
+
609
+    /**
610
+     * @param string $path
611
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
612
+     * @param \OCP\Lock\ILockingProvider $provider
613
+     */
614
+    public function changeLock($path, $type, ILockingProvider $provider) {
615
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
616
+            $this->getWrapperStorage()->changeLock($path, $type, $provider);
617
+        }
618
+    }
619
+
620
+    /**
621
+     * @return bool
622
+     */
623
+    public function needsPartFile() {
624
+        return $this->getWrapperStorage()->needsPartFile();
625
+    }
626
+
627
+    public function writeStream(string $path, $stream, int $size = null): int {
628
+        $storage = $this->getWrapperStorage();
629
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
630
+            /** @var IWriteStreamStorage $storage */
631
+            return $storage->writeStream($path, $stream, $size);
632
+        } else {
633
+            $target = $this->fopen($path, 'w');
634
+            list($count, $result) = \OC_Helper::streamCopy($stream, $target);
635
+            fclose($stream);
636
+            fclose($target);
637
+            return $count;
638
+        }
639
+    }
640
+
641
+    public function getDirectoryContent($directory): \Traversable {
642
+        return $this->getWrapperStorage()->getDirectoryContent($directory);
643
+    }
644 644
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Jail.php 1 patch
Indentation   +500 added lines, -500 removed lines patch added patch discarded remove patch
@@ -39,504 +39,504 @@
 block discarded – undo
39 39
  * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
40 40
  */
41 41
 class Jail extends Wrapper {
42
-	/**
43
-	 * @var string
44
-	 */
45
-	protected $rootPath;
46
-
47
-	/**
48
-	 * @param array $arguments ['storage' => $storage, 'mask' => $root]
49
-	 *
50
-	 * $storage: The storage that will be wrapper
51
-	 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
52
-	 */
53
-	public function __construct($arguments) {
54
-		parent::__construct($arguments);
55
-		$this->rootPath = $arguments['root'];
56
-	}
57
-
58
-	public function getUnjailedPath($path) {
59
-		return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
60
-	}
61
-
62
-	/**
63
-	 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
64
-	 */
65
-	public function getUnjailedStorage() {
66
-		return $this->storage;
67
-	}
68
-
69
-
70
-	public function getJailedPath($path) {
71
-		$root = rtrim($this->rootPath, '/') . '/';
72
-
73
-		if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
74
-			return null;
75
-		} else {
76
-			$path = substr($path, strlen($this->rootPath));
77
-			return trim($path, '/');
78
-		}
79
-	}
80
-
81
-	public function getId() {
82
-		return parent::getId();
83
-	}
84
-
85
-	/**
86
-	 * see https://www.php.net/manual/en/function.mkdir.php
87
-	 *
88
-	 * @param string $path
89
-	 * @return bool
90
-	 */
91
-	public function mkdir($path) {
92
-		return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
93
-	}
94
-
95
-	/**
96
-	 * see https://www.php.net/manual/en/function.rmdir.php
97
-	 *
98
-	 * @param string $path
99
-	 * @return bool
100
-	 */
101
-	public function rmdir($path) {
102
-		return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
103
-	}
104
-
105
-	/**
106
-	 * see https://www.php.net/manual/en/function.opendir.php
107
-	 *
108
-	 * @param string $path
109
-	 * @return resource|bool
110
-	 */
111
-	public function opendir($path) {
112
-		return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
113
-	}
114
-
115
-	/**
116
-	 * see https://www.php.net/manual/en/function.is_dir.php
117
-	 *
118
-	 * @param string $path
119
-	 * @return bool
120
-	 */
121
-	public function is_dir($path) {
122
-		return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
123
-	}
124
-
125
-	/**
126
-	 * see https://www.php.net/manual/en/function.is_file.php
127
-	 *
128
-	 * @param string $path
129
-	 * @return bool
130
-	 */
131
-	public function is_file($path) {
132
-		return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
133
-	}
134
-
135
-	/**
136
-	 * see https://www.php.net/manual/en/function.stat.php
137
-	 * only the following keys are required in the result: size and mtime
138
-	 *
139
-	 * @param string $path
140
-	 * @return array|bool
141
-	 */
142
-	public function stat($path) {
143
-		return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
144
-	}
145
-
146
-	/**
147
-	 * see https://www.php.net/manual/en/function.filetype.php
148
-	 *
149
-	 * @param string $path
150
-	 * @return bool
151
-	 */
152
-	public function filetype($path) {
153
-		return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
154
-	}
155
-
156
-	/**
157
-	 * see https://www.php.net/manual/en/function.filesize.php
158
-	 * The result for filesize when called on a folder is required to be 0
159
-	 *
160
-	 * @param string $path
161
-	 * @return int|bool
162
-	 */
163
-	public function filesize($path) {
164
-		return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
165
-	}
166
-
167
-	/**
168
-	 * check if a file can be created in $path
169
-	 *
170
-	 * @param string $path
171
-	 * @return bool
172
-	 */
173
-	public function isCreatable($path) {
174
-		return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
175
-	}
176
-
177
-	/**
178
-	 * check if a file can be read
179
-	 *
180
-	 * @param string $path
181
-	 * @return bool
182
-	 */
183
-	public function isReadable($path) {
184
-		return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
185
-	}
186
-
187
-	/**
188
-	 * check if a file can be written to
189
-	 *
190
-	 * @param string $path
191
-	 * @return bool
192
-	 */
193
-	public function isUpdatable($path) {
194
-		return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
195
-	}
196
-
197
-	/**
198
-	 * check if a file can be deleted
199
-	 *
200
-	 * @param string $path
201
-	 * @return bool
202
-	 */
203
-	public function isDeletable($path) {
204
-		return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
205
-	}
206
-
207
-	/**
208
-	 * check if a file can be shared
209
-	 *
210
-	 * @param string $path
211
-	 * @return bool
212
-	 */
213
-	public function isSharable($path) {
214
-		return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
215
-	}
216
-
217
-	/**
218
-	 * get the full permissions of a path.
219
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
220
-	 *
221
-	 * @param string $path
222
-	 * @return int
223
-	 */
224
-	public function getPermissions($path) {
225
-		return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
226
-	}
227
-
228
-	/**
229
-	 * see https://www.php.net/manual/en/function.file_exists.php
230
-	 *
231
-	 * @param string $path
232
-	 * @return bool
233
-	 */
234
-	public function file_exists($path) {
235
-		return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
236
-	}
237
-
238
-	/**
239
-	 * see https://www.php.net/manual/en/function.filemtime.php
240
-	 *
241
-	 * @param string $path
242
-	 * @return int|bool
243
-	 */
244
-	public function filemtime($path) {
245
-		return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
246
-	}
247
-
248
-	/**
249
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
250
-	 *
251
-	 * @param string $path
252
-	 * @return string|bool
253
-	 */
254
-	public function file_get_contents($path) {
255
-		return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
256
-	}
257
-
258
-	/**
259
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
260
-	 *
261
-	 * @param string $path
262
-	 * @param mixed $data
263
-	 * @return int|false
264
-	 */
265
-	public function file_put_contents($path, $data) {
266
-		return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
267
-	}
268
-
269
-	/**
270
-	 * see https://www.php.net/manual/en/function.unlink.php
271
-	 *
272
-	 * @param string $path
273
-	 * @return bool
274
-	 */
275
-	public function unlink($path) {
276
-		return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
277
-	}
278
-
279
-	/**
280
-	 * see https://www.php.net/manual/en/function.rename.php
281
-	 *
282
-	 * @param string $path1
283
-	 * @param string $path2
284
-	 * @return bool
285
-	 */
286
-	public function rename($path1, $path2) {
287
-		return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
288
-	}
289
-
290
-	/**
291
-	 * see https://www.php.net/manual/en/function.copy.php
292
-	 *
293
-	 * @param string $path1
294
-	 * @param string $path2
295
-	 * @return bool
296
-	 */
297
-	public function copy($path1, $path2) {
298
-		return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
299
-	}
300
-
301
-	/**
302
-	 * see https://www.php.net/manual/en/function.fopen.php
303
-	 *
304
-	 * @param string $path
305
-	 * @param string $mode
306
-	 * @return resource|bool
307
-	 */
308
-	public function fopen($path, $mode) {
309
-		return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
310
-	}
311
-
312
-	/**
313
-	 * get the mimetype for a file or folder
314
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
315
-	 *
316
-	 * @param string $path
317
-	 * @return string|bool
318
-	 */
319
-	public function getMimeType($path) {
320
-		return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
321
-	}
322
-
323
-	/**
324
-	 * see https://www.php.net/manual/en/function.hash.php
325
-	 *
326
-	 * @param string $type
327
-	 * @param string $path
328
-	 * @param bool $raw
329
-	 * @return string|bool
330
-	 */
331
-	public function hash($type, $path, $raw = false) {
332
-		return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
333
-	}
334
-
335
-	/**
336
-	 * see https://www.php.net/manual/en/function.free_space.php
337
-	 *
338
-	 * @param string $path
339
-	 * @return int|bool
340
-	 */
341
-	public function free_space($path) {
342
-		return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
343
-	}
344
-
345
-	/**
346
-	 * search for occurrences of $query in file names
347
-	 *
348
-	 * @param string $query
349
-	 * @return array|bool
350
-	 */
351
-	public function search($query) {
352
-		return $this->getWrapperStorage()->search($query);
353
-	}
354
-
355
-	/**
356
-	 * see https://www.php.net/manual/en/function.touch.php
357
-	 * If the backend does not support the operation, false should be returned
358
-	 *
359
-	 * @param string $path
360
-	 * @param int $mtime
361
-	 * @return bool
362
-	 */
363
-	public function touch($path, $mtime = null) {
364
-		return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
365
-	}
366
-
367
-	/**
368
-	 * get the path to a local version of the file.
369
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
370
-	 *
371
-	 * @param string $path
372
-	 * @return string|bool
373
-	 */
374
-	public function getLocalFile($path) {
375
-		return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
376
-	}
377
-
378
-	/**
379
-	 * check if a file or folder has been updated since $time
380
-	 *
381
-	 * @param string $path
382
-	 * @param int $time
383
-	 * @return bool
384
-	 *
385
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
386
-	 * returning true for other changes in the folder is optional
387
-	 */
388
-	public function hasUpdated($path, $time) {
389
-		return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
390
-	}
391
-
392
-	/**
393
-	 * get a cache instance for the storage
394
-	 *
395
-	 * @param string $path
396
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
397
-	 * @return \OC\Files\Cache\Cache
398
-	 */
399
-	public function getCache($path = '', $storage = null) {
400
-		if (!$storage) {
401
-			$storage = $this->getWrapperStorage();
402
-		}
403
-		$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
404
-		return new CacheJail($sourceCache, $this->rootPath);
405
-	}
406
-
407
-	/**
408
-	 * get the user id of the owner of a file or folder
409
-	 *
410
-	 * @param string $path
411
-	 * @return string
412
-	 */
413
-	public function getOwner($path) {
414
-		return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
415
-	}
416
-
417
-	/**
418
-	 * get a watcher instance for the cache
419
-	 *
420
-	 * @param string $path
421
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
422
-	 * @return \OC\Files\Cache\Watcher
423
-	 */
424
-	public function getWatcher($path = '', $storage = null) {
425
-		if (!$storage) {
426
-			$storage = $this;
427
-		}
428
-		return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
429
-	}
430
-
431
-	/**
432
-	 * get the ETag for a file or folder
433
-	 *
434
-	 * @param string $path
435
-	 * @return string|bool
436
-	 */
437
-	public function getETag($path) {
438
-		return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
439
-	}
440
-
441
-	/**
442
-	 * @param string $path
443
-	 * @return array
444
-	 */
445
-	public function getMetaData($path) {
446
-		return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
447
-	}
448
-
449
-	/**
450
-	 * @param string $path
451
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
452
-	 * @param \OCP\Lock\ILockingProvider $provider
453
-	 * @throws \OCP\Lock\LockedException
454
-	 */
455
-	public function acquireLock($path, $type, ILockingProvider $provider) {
456
-		$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
457
-	}
458
-
459
-	/**
460
-	 * @param string $path
461
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
462
-	 * @param \OCP\Lock\ILockingProvider $provider
463
-	 */
464
-	public function releaseLock($path, $type, ILockingProvider $provider) {
465
-		$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
466
-	}
467
-
468
-	/**
469
-	 * @param string $path
470
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
471
-	 * @param \OCP\Lock\ILockingProvider $provider
472
-	 */
473
-	public function changeLock($path, $type, ILockingProvider $provider) {
474
-		$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
475
-	}
476
-
477
-	/**
478
-	 * Resolve the path for the source of the share
479
-	 *
480
-	 * @param string $path
481
-	 * @return array
482
-	 */
483
-	public function resolvePath($path) {
484
-		return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
485
-	}
486
-
487
-	/**
488
-	 * @param IStorage $sourceStorage
489
-	 * @param string $sourceInternalPath
490
-	 * @param string $targetInternalPath
491
-	 * @return bool
492
-	 */
493
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
494
-		if ($sourceStorage === $this) {
495
-			return $this->copy($sourceInternalPath, $targetInternalPath);
496
-		}
497
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
498
-	}
499
-
500
-	/**
501
-	 * @param IStorage $sourceStorage
502
-	 * @param string $sourceInternalPath
503
-	 * @param string $targetInternalPath
504
-	 * @return bool
505
-	 */
506
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
507
-		if ($sourceStorage === $this) {
508
-			return $this->rename($sourceInternalPath, $targetInternalPath);
509
-		}
510
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
511
-	}
512
-
513
-	public function getPropagator($storage = null) {
514
-		if (isset($this->propagator)) {
515
-			return $this->propagator;
516
-		}
517
-
518
-		if (!$storage) {
519
-			$storage = $this;
520
-		}
521
-		$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
522
-		return $this->propagator;
523
-	}
524
-
525
-	public function writeStream(string $path, $stream, int $size = null): int {
526
-		$storage = $this->getWrapperStorage();
527
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
528
-			/** @var IWriteStreamStorage $storage */
529
-			return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
530
-		} else {
531
-			$target = $this->fopen($path, 'w');
532
-			list($count, $result) = \OC_Helper::streamCopy($stream, $target);
533
-			fclose($stream);
534
-			fclose($target);
535
-			return $count;
536
-		}
537
-	}
538
-
539
-	public function getDirectoryContent($directory): \Traversable {
540
-		return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
541
-	}
42
+    /**
43
+     * @var string
44
+     */
45
+    protected $rootPath;
46
+
47
+    /**
48
+     * @param array $arguments ['storage' => $storage, 'mask' => $root]
49
+     *
50
+     * $storage: The storage that will be wrapper
51
+     * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
52
+     */
53
+    public function __construct($arguments) {
54
+        parent::__construct($arguments);
55
+        $this->rootPath = $arguments['root'];
56
+    }
57
+
58
+    public function getUnjailedPath($path) {
59
+        return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
60
+    }
61
+
62
+    /**
63
+     * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
64
+     */
65
+    public function getUnjailedStorage() {
66
+        return $this->storage;
67
+    }
68
+
69
+
70
+    public function getJailedPath($path) {
71
+        $root = rtrim($this->rootPath, '/') . '/';
72
+
73
+        if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
74
+            return null;
75
+        } else {
76
+            $path = substr($path, strlen($this->rootPath));
77
+            return trim($path, '/');
78
+        }
79
+    }
80
+
81
+    public function getId() {
82
+        return parent::getId();
83
+    }
84
+
85
+    /**
86
+     * see https://www.php.net/manual/en/function.mkdir.php
87
+     *
88
+     * @param string $path
89
+     * @return bool
90
+     */
91
+    public function mkdir($path) {
92
+        return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
93
+    }
94
+
95
+    /**
96
+     * see https://www.php.net/manual/en/function.rmdir.php
97
+     *
98
+     * @param string $path
99
+     * @return bool
100
+     */
101
+    public function rmdir($path) {
102
+        return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
103
+    }
104
+
105
+    /**
106
+     * see https://www.php.net/manual/en/function.opendir.php
107
+     *
108
+     * @param string $path
109
+     * @return resource|bool
110
+     */
111
+    public function opendir($path) {
112
+        return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
113
+    }
114
+
115
+    /**
116
+     * see https://www.php.net/manual/en/function.is_dir.php
117
+     *
118
+     * @param string $path
119
+     * @return bool
120
+     */
121
+    public function is_dir($path) {
122
+        return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
123
+    }
124
+
125
+    /**
126
+     * see https://www.php.net/manual/en/function.is_file.php
127
+     *
128
+     * @param string $path
129
+     * @return bool
130
+     */
131
+    public function is_file($path) {
132
+        return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
133
+    }
134
+
135
+    /**
136
+     * see https://www.php.net/manual/en/function.stat.php
137
+     * only the following keys are required in the result: size and mtime
138
+     *
139
+     * @param string $path
140
+     * @return array|bool
141
+     */
142
+    public function stat($path) {
143
+        return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
144
+    }
145
+
146
+    /**
147
+     * see https://www.php.net/manual/en/function.filetype.php
148
+     *
149
+     * @param string $path
150
+     * @return bool
151
+     */
152
+    public function filetype($path) {
153
+        return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
154
+    }
155
+
156
+    /**
157
+     * see https://www.php.net/manual/en/function.filesize.php
158
+     * The result for filesize when called on a folder is required to be 0
159
+     *
160
+     * @param string $path
161
+     * @return int|bool
162
+     */
163
+    public function filesize($path) {
164
+        return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
165
+    }
166
+
167
+    /**
168
+     * check if a file can be created in $path
169
+     *
170
+     * @param string $path
171
+     * @return bool
172
+     */
173
+    public function isCreatable($path) {
174
+        return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
175
+    }
176
+
177
+    /**
178
+     * check if a file can be read
179
+     *
180
+     * @param string $path
181
+     * @return bool
182
+     */
183
+    public function isReadable($path) {
184
+        return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
185
+    }
186
+
187
+    /**
188
+     * check if a file can be written to
189
+     *
190
+     * @param string $path
191
+     * @return bool
192
+     */
193
+    public function isUpdatable($path) {
194
+        return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
195
+    }
196
+
197
+    /**
198
+     * check if a file can be deleted
199
+     *
200
+     * @param string $path
201
+     * @return bool
202
+     */
203
+    public function isDeletable($path) {
204
+        return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
205
+    }
206
+
207
+    /**
208
+     * check if a file can be shared
209
+     *
210
+     * @param string $path
211
+     * @return bool
212
+     */
213
+    public function isSharable($path) {
214
+        return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
215
+    }
216
+
217
+    /**
218
+     * get the full permissions of a path.
219
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
220
+     *
221
+     * @param string $path
222
+     * @return int
223
+     */
224
+    public function getPermissions($path) {
225
+        return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
226
+    }
227
+
228
+    /**
229
+     * see https://www.php.net/manual/en/function.file_exists.php
230
+     *
231
+     * @param string $path
232
+     * @return bool
233
+     */
234
+    public function file_exists($path) {
235
+        return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
236
+    }
237
+
238
+    /**
239
+     * see https://www.php.net/manual/en/function.filemtime.php
240
+     *
241
+     * @param string $path
242
+     * @return int|bool
243
+     */
244
+    public function filemtime($path) {
245
+        return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
246
+    }
247
+
248
+    /**
249
+     * see https://www.php.net/manual/en/function.file_get_contents.php
250
+     *
251
+     * @param string $path
252
+     * @return string|bool
253
+     */
254
+    public function file_get_contents($path) {
255
+        return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
256
+    }
257
+
258
+    /**
259
+     * see https://www.php.net/manual/en/function.file_put_contents.php
260
+     *
261
+     * @param string $path
262
+     * @param mixed $data
263
+     * @return int|false
264
+     */
265
+    public function file_put_contents($path, $data) {
266
+        return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
267
+    }
268
+
269
+    /**
270
+     * see https://www.php.net/manual/en/function.unlink.php
271
+     *
272
+     * @param string $path
273
+     * @return bool
274
+     */
275
+    public function unlink($path) {
276
+        return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
277
+    }
278
+
279
+    /**
280
+     * see https://www.php.net/manual/en/function.rename.php
281
+     *
282
+     * @param string $path1
283
+     * @param string $path2
284
+     * @return bool
285
+     */
286
+    public function rename($path1, $path2) {
287
+        return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
288
+    }
289
+
290
+    /**
291
+     * see https://www.php.net/manual/en/function.copy.php
292
+     *
293
+     * @param string $path1
294
+     * @param string $path2
295
+     * @return bool
296
+     */
297
+    public function copy($path1, $path2) {
298
+        return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
299
+    }
300
+
301
+    /**
302
+     * see https://www.php.net/manual/en/function.fopen.php
303
+     *
304
+     * @param string $path
305
+     * @param string $mode
306
+     * @return resource|bool
307
+     */
308
+    public function fopen($path, $mode) {
309
+        return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
310
+    }
311
+
312
+    /**
313
+     * get the mimetype for a file or folder
314
+     * The mimetype for a folder is required to be "httpd/unix-directory"
315
+     *
316
+     * @param string $path
317
+     * @return string|bool
318
+     */
319
+    public function getMimeType($path) {
320
+        return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
321
+    }
322
+
323
+    /**
324
+     * see https://www.php.net/manual/en/function.hash.php
325
+     *
326
+     * @param string $type
327
+     * @param string $path
328
+     * @param bool $raw
329
+     * @return string|bool
330
+     */
331
+    public function hash($type, $path, $raw = false) {
332
+        return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
333
+    }
334
+
335
+    /**
336
+     * see https://www.php.net/manual/en/function.free_space.php
337
+     *
338
+     * @param string $path
339
+     * @return int|bool
340
+     */
341
+    public function free_space($path) {
342
+        return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
343
+    }
344
+
345
+    /**
346
+     * search for occurrences of $query in file names
347
+     *
348
+     * @param string $query
349
+     * @return array|bool
350
+     */
351
+    public function search($query) {
352
+        return $this->getWrapperStorage()->search($query);
353
+    }
354
+
355
+    /**
356
+     * see https://www.php.net/manual/en/function.touch.php
357
+     * If the backend does not support the operation, false should be returned
358
+     *
359
+     * @param string $path
360
+     * @param int $mtime
361
+     * @return bool
362
+     */
363
+    public function touch($path, $mtime = null) {
364
+        return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
365
+    }
366
+
367
+    /**
368
+     * get the path to a local version of the file.
369
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
370
+     *
371
+     * @param string $path
372
+     * @return string|bool
373
+     */
374
+    public function getLocalFile($path) {
375
+        return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
376
+    }
377
+
378
+    /**
379
+     * check if a file or folder has been updated since $time
380
+     *
381
+     * @param string $path
382
+     * @param int $time
383
+     * @return bool
384
+     *
385
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
386
+     * returning true for other changes in the folder is optional
387
+     */
388
+    public function hasUpdated($path, $time) {
389
+        return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
390
+    }
391
+
392
+    /**
393
+     * get a cache instance for the storage
394
+     *
395
+     * @param string $path
396
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
397
+     * @return \OC\Files\Cache\Cache
398
+     */
399
+    public function getCache($path = '', $storage = null) {
400
+        if (!$storage) {
401
+            $storage = $this->getWrapperStorage();
402
+        }
403
+        $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
404
+        return new CacheJail($sourceCache, $this->rootPath);
405
+    }
406
+
407
+    /**
408
+     * get the user id of the owner of a file or folder
409
+     *
410
+     * @param string $path
411
+     * @return string
412
+     */
413
+    public function getOwner($path) {
414
+        return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
415
+    }
416
+
417
+    /**
418
+     * get a watcher instance for the cache
419
+     *
420
+     * @param string $path
421
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
422
+     * @return \OC\Files\Cache\Watcher
423
+     */
424
+    public function getWatcher($path = '', $storage = null) {
425
+        if (!$storage) {
426
+            $storage = $this;
427
+        }
428
+        return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
429
+    }
430
+
431
+    /**
432
+     * get the ETag for a file or folder
433
+     *
434
+     * @param string $path
435
+     * @return string|bool
436
+     */
437
+    public function getETag($path) {
438
+        return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
439
+    }
440
+
441
+    /**
442
+     * @param string $path
443
+     * @return array
444
+     */
445
+    public function getMetaData($path) {
446
+        return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
447
+    }
448
+
449
+    /**
450
+     * @param string $path
451
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
452
+     * @param \OCP\Lock\ILockingProvider $provider
453
+     * @throws \OCP\Lock\LockedException
454
+     */
455
+    public function acquireLock($path, $type, ILockingProvider $provider) {
456
+        $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
457
+    }
458
+
459
+    /**
460
+     * @param string $path
461
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
462
+     * @param \OCP\Lock\ILockingProvider $provider
463
+     */
464
+    public function releaseLock($path, $type, ILockingProvider $provider) {
465
+        $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
466
+    }
467
+
468
+    /**
469
+     * @param string $path
470
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
471
+     * @param \OCP\Lock\ILockingProvider $provider
472
+     */
473
+    public function changeLock($path, $type, ILockingProvider $provider) {
474
+        $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
475
+    }
476
+
477
+    /**
478
+     * Resolve the path for the source of the share
479
+     *
480
+     * @param string $path
481
+     * @return array
482
+     */
483
+    public function resolvePath($path) {
484
+        return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
485
+    }
486
+
487
+    /**
488
+     * @param IStorage $sourceStorage
489
+     * @param string $sourceInternalPath
490
+     * @param string $targetInternalPath
491
+     * @return bool
492
+     */
493
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
494
+        if ($sourceStorage === $this) {
495
+            return $this->copy($sourceInternalPath, $targetInternalPath);
496
+        }
497
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
498
+    }
499
+
500
+    /**
501
+     * @param IStorage $sourceStorage
502
+     * @param string $sourceInternalPath
503
+     * @param string $targetInternalPath
504
+     * @return bool
505
+     */
506
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
507
+        if ($sourceStorage === $this) {
508
+            return $this->rename($sourceInternalPath, $targetInternalPath);
509
+        }
510
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
511
+    }
512
+
513
+    public function getPropagator($storage = null) {
514
+        if (isset($this->propagator)) {
515
+            return $this->propagator;
516
+        }
517
+
518
+        if (!$storage) {
519
+            $storage = $this;
520
+        }
521
+        $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
522
+        return $this->propagator;
523
+    }
524
+
525
+    public function writeStream(string $path, $stream, int $size = null): int {
526
+        $storage = $this->getWrapperStorage();
527
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
528
+            /** @var IWriteStreamStorage $storage */
529
+            return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
530
+        } else {
531
+            $target = $this->fopen($path, 'w');
532
+            list($count, $result) = \OC_Helper::streamCopy($stream, $target);
533
+            fclose($stream);
534
+            fclose($target);
535
+            return $count;
536
+        }
537
+    }
538
+
539
+    public function getDirectoryContent($directory): \Traversable {
540
+        return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
541
+    }
542 542
 }
Please login to merge, or discard this patch.
lib/private/Files/Stream/Quota.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -34,70 +34,70 @@
 block discarded – undo
34 34
  * usage: resource \OC\Files\Stream\Quota::wrap($stream, $limit)
35 35
  */
36 36
 class Quota extends Wrapper {
37
-	/**
38
-	 * @var int $limit
39
-	 */
40
-	private $limit;
37
+    /**
38
+     * @var int $limit
39
+     */
40
+    private $limit;
41 41
 
42
-	/**
43
-	 * @param resource $stream
44
-	 * @param int $limit
45
-	 * @return bool|resource
46
-	 */
47
-	public static function wrap($stream, $limit) {
48
-		$context = stream_context_create([
49
-			'quota' => [
50
-				'source' => $stream,
51
-				'limit' => $limit
52
-			]
53
-		]);
54
-		return Wrapper::wrapSource($stream, $context, 'quota', self::class);
55
-	}
42
+    /**
43
+     * @param resource $stream
44
+     * @param int $limit
45
+     * @return bool|resource
46
+     */
47
+    public static function wrap($stream, $limit) {
48
+        $context = stream_context_create([
49
+            'quota' => [
50
+                'source' => $stream,
51
+                'limit' => $limit
52
+            ]
53
+        ]);
54
+        return Wrapper::wrapSource($stream, $context, 'quota', self::class);
55
+    }
56 56
 
57
-	public function stream_open($path, $mode, $options, &$opened_path) {
58
-		$context = $this->loadContext('quota');
59
-		$this->source = $context['source'];
60
-		$this->limit = $context['limit'];
57
+    public function stream_open($path, $mode, $options, &$opened_path) {
58
+        $context = $this->loadContext('quota');
59
+        $this->source = $context['source'];
60
+        $this->limit = $context['limit'];
61 61
 
62
-		return true;
63
-	}
62
+        return true;
63
+    }
64 64
 
65
-	public function dir_opendir($path, $options) {
66
-		return false;
67
-	}
65
+    public function dir_opendir($path, $options) {
66
+        return false;
67
+    }
68 68
 
69
-	public function stream_seek($offset, $whence = SEEK_SET) {
70
-		if ($whence === SEEK_END) {
71
-			// go to the end to find out last position's offset
72
-			$oldOffset = $this->stream_tell();
73
-			if (fseek($this->source, 0, $whence) !== 0) {
74
-				return false;
75
-			}
76
-			$whence = SEEK_SET;
77
-			$offset = $this->stream_tell() + $offset;
78
-			$this->limit += $oldOffset - $offset;
79
-		} elseif ($whence === SEEK_SET) {
80
-			$this->limit += $this->stream_tell() - $offset;
81
-		} else {
82
-			$this->limit -= $offset;
83
-		}
84
-		// this wrapper needs to return "true" for success.
85
-		// the fseek call itself returns 0 on succeess
86
-		return fseek($this->source, $offset, $whence) === 0;
87
-	}
69
+    public function stream_seek($offset, $whence = SEEK_SET) {
70
+        if ($whence === SEEK_END) {
71
+            // go to the end to find out last position's offset
72
+            $oldOffset = $this->stream_tell();
73
+            if (fseek($this->source, 0, $whence) !== 0) {
74
+                return false;
75
+            }
76
+            $whence = SEEK_SET;
77
+            $offset = $this->stream_tell() + $offset;
78
+            $this->limit += $oldOffset - $offset;
79
+        } elseif ($whence === SEEK_SET) {
80
+            $this->limit += $this->stream_tell() - $offset;
81
+        } else {
82
+            $this->limit -= $offset;
83
+        }
84
+        // this wrapper needs to return "true" for success.
85
+        // the fseek call itself returns 0 on succeess
86
+        return fseek($this->source, $offset, $whence) === 0;
87
+    }
88 88
 
89
-	public function stream_read($count) {
90
-		$this->limit -= $count;
91
-		return fread($this->source, $count);
92
-	}
89
+    public function stream_read($count) {
90
+        $this->limit -= $count;
91
+        return fread($this->source, $count);
92
+    }
93 93
 
94
-	public function stream_write($data) {
95
-		$size = strlen($data);
96
-		if ($size > $this->limit) {
97
-			$data = substr($data, 0, $this->limit);
98
-			$size = $this->limit;
99
-		}
100
-		$this->limit -= $size;
101
-		return fwrite($this->source, $data);
102
-	}
94
+    public function stream_write($data) {
95
+        $size = strlen($data);
96
+        if ($size > $this->limit) {
97
+            $data = substr($data, 0, $this->limit);
98
+            $size = $this->limit;
99
+        }
100
+        $this->limit -= $size;
101
+        return fwrite($this->source, $data);
102
+    }
103 103
 }
Please login to merge, or discard this patch.