Passed
Push — master ( 927582...173f8a )
by Morris
11:22 queued 17s
created

Common::getAvailability()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Greta Doci <[email protected]>
10
 * @author hkjolhede <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Jörn Friedrich Dreyer <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Martin Mattel <[email protected]>
15
 * @author Michael Gapczynski <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Robin McCorkell <[email protected]>
19
 * @author Roeland Jago Douma <[email protected]>
20
 * @author Roland Tapken <[email protected]>
21
 * @author Sam Tuke <[email protected]>
22
 * @author scambra <[email protected]>
23
 * @author Stefan Weil <[email protected]>
24
 * @author Thomas Müller <[email protected]>
25
 * @author Vincent Petry <[email protected]>
26
 * @author Vinicius Cubas Brand <[email protected]>
27
 *
28
 * @license AGPL-3.0
29
 *
30
 * This code is free software: you can redistribute it and/or modify
31
 * it under the terms of the GNU Affero General Public License, version 3,
32
 * as published by the Free Software Foundation.
33
 *
34
 * This program is distributed in the hope that it will be useful,
35
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37
 * GNU Affero General Public License for more details.
38
 *
39
 * You should have received a copy of the GNU Affero General Public License, version 3,
40
 * along with this program. If not, see <http://www.gnu.org/licenses/>
41
 *
42
 */
43
44
namespace OC\Files\Storage;
45
46
use OC\Files\Cache\Cache;
47
use OC\Files\Cache\Propagator;
48
use OC\Files\Cache\Scanner;
49
use OC\Files\Cache\Updater;
50
use OC\Files\Cache\Watcher;
51
use OC\Files\Filesystem;
52
use OC\Files\Storage\Wrapper\Jail;
53
use OC\Files\Storage\Wrapper\Wrapper;
54
use OCP\Files\EmptyFileNameException;
55
use OCP\Files\FileNameTooLongException;
56
use OCP\Files\GenericFileException;
57
use OCP\Files\InvalidCharacterInPathException;
58
use OCP\Files\InvalidDirectoryException;
59
use OCP\Files\InvalidPathException;
60
use OCP\Files\ReservedWordException;
61
use OCP\Files\Storage\ILockingStorage;
62
use OCP\Files\Storage\IStorage;
63
use OCP\Files\Storage\IWriteStreamStorage;
64
use OCP\ILogger;
65
use OCP\Lock\ILockingProvider;
66
use OCP\Lock\LockedException;
67
68
/**
69
 * Storage backend class for providing common filesystem operation methods
70
 * which are not storage-backend specific.
71
 *
72
 * \OC\Files\Storage\Common is never used directly; it is extended by all other
73
 * storage backends, where its methods may be overridden, and additional
74
 * (backend-specific) methods are defined.
75
 *
76
 * Some \OC\Files\Storage\Common methods call functions which are first defined
77
 * in classes which extend it, e.g. $this->stat() .
78
 */
79
abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
80
	use LocalTempFileTrait;
81
82
	protected $cache;
83
	protected $scanner;
84
	protected $watcher;
85
	protected $propagator;
86
	protected $storageCache;
87
	protected $updater;
88
89
	protected $mountOptions = [];
90
	protected $owner = null;
91
92
	private $shouldLogLocks = null;
93
	private $logger;
94
95
	public function __construct($parameters) {
96
	}
97
98
	/**
99
	 * Remove a file or folder
100
	 *
101
	 * @param string $path
102
	 * @return bool
103
	 */
104
	protected function remove($path) {
105
		if ($this->is_dir($path)) {
106
			return $this->rmdir($path);
107
		} elseif ($this->is_file($path)) {
108
			return $this->unlink($path);
109
		} else {
110
			return false;
111
		}
112
	}
113
114
	public function is_dir($path) {
115
		return $this->filetype($path) === 'dir';
116
	}
117
118
	public function is_file($path) {
119
		return $this->filetype($path) === 'file';
120
	}
121
122
	public function filesize($path) {
123
		if ($this->is_dir($path)) {
124
			return 0; //by definition
125
		} else {
126
			$stat = $this->stat($path);
127
			if (isset($stat['size'])) {
128
				return $stat['size'];
129
			} else {
130
				return 0;
131
			}
132
		}
133
	}
134
135
	public function isReadable($path) {
136
		// at least check whether it exists
137
		// subclasses might want to implement this more thoroughly
138
		return $this->file_exists($path);
139
	}
140
141
	public function isUpdatable($path) {
142
		// at least check whether it exists
143
		// subclasses might want to implement this more thoroughly
144
		// a non-existing file/folder isn't updatable
145
		return $this->file_exists($path);
146
	}
147
148
	public function isCreatable($path) {
149
		if ($this->is_dir($path) && $this->isUpdatable($path)) {
150
			return true;
151
		}
152
		return false;
153
	}
154
155
	public function isDeletable($path) {
156
		if ($path === '' || $path === '/') {
157
			return false;
158
		}
159
		$parent = dirname($path);
160
		return $this->isUpdatable($parent) && $this->isUpdatable($path);
161
	}
162
163
	public function isSharable($path) {
164
		return $this->isReadable($path);
165
	}
166
167
	public function getPermissions($path) {
168
		$permissions = 0;
169
		if ($this->isCreatable($path)) {
170
			$permissions |= \OCP\Constants::PERMISSION_CREATE;
171
		}
172
		if ($this->isReadable($path)) {
173
			$permissions |= \OCP\Constants::PERMISSION_READ;
174
		}
175
		if ($this->isUpdatable($path)) {
176
			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
177
		}
178
		if ($this->isDeletable($path)) {
179
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
180
		}
181
		if ($this->isSharable($path)) {
182
			$permissions |= \OCP\Constants::PERMISSION_SHARE;
183
		}
184
		return $permissions;
185
	}
186
187
	public function filemtime($path) {
188
		$stat = $this->stat($path);
189
		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
190
			return $stat['mtime'];
191
		} else {
192
			return 0;
193
		}
194
	}
195
196
	public function file_get_contents($path) {
197
		$handle = $this->fopen($path, "r");
198
		if (!$handle) {
0 ignored issues
show
introduced by
$handle is of type false|resource, thus it always evaluated to false.
Loading history...
199
			return false;
200
		}
201
		$data = stream_get_contents($handle);
202
		fclose($handle);
203
		return $data;
204
	}
205
206
	public function file_put_contents($path, $data) {
207
		$handle = $this->fopen($path, "w");
208
		$this->removeCachedFile($path);
209
		$count = fwrite($handle, $data);
210
		fclose($handle);
211
		return $count;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $count returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::file_put_contents() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
212
	}
213
214
	public function rename($path1, $path2) {
215
		$this->remove($path2);
216
217
		$this->removeCachedFile($path1);
218
		return $this->copy($path1, $path2) and $this->remove($path1);
219
	}
220
221
	public function copy($path1, $path2) {
222
		if ($this->is_dir($path1)) {
223
			$this->remove($path2);
224
			$dir = $this->opendir($path1);
225
			$this->mkdir($path2);
226
			while ($file = readdir($dir)) {
227
				if (!Filesystem::isIgnoredDir($file)) {
228
					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
229
						return false;
230
					}
231
				}
232
			}
233
			closedir($dir);
234
			return true;
235
		} else {
236
			$source = $this->fopen($path1, 'r');
237
			$target = $this->fopen($path2, 'w');
238
			[, $result] = \OC_Helper::streamCopy($source, $target);
239
			if (!$result) {
0 ignored issues
show
introduced by
The condition $result is always false.
Loading history...
240
				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

240
				/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
Loading history...
241
			}
242
			$this->removeCachedFile($path2);
243
			return $result;
244
		}
245
	}
246
247
	public function getMimeType($path) {
248
		if ($this->is_dir($path)) {
249
			return 'httpd/unix-directory';
250
		} elseif ($this->file_exists($path)) {
251
			return \OC::$server->getMimeTypeDetector()->detectPath($path);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getMimeTypeDetector() has been deprecated. ( Ignorable by Annotation )

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

251
			return /** @scrutinizer ignore-deprecated */ \OC::$server->getMimeTypeDetector()->detectPath($path);
Loading history...
252
		} else {
253
			return false;
254
		}
255
	}
256
257
	public function hash($type, $path, $raw = false) {
258
		$fh = $this->fopen($path, 'rb');
259
		$ctx = hash_init($type);
260
		hash_update_stream($ctx, $fh);
261
		fclose($fh);
262
		return hash_final($ctx, $raw);
263
	}
264
265
	public function search($query) {
266
		return $this->searchInDir($query);
267
	}
268
269
	public function getLocalFile($path) {
270
		return $this->getCachedFile($path);
271
	}
272
273
	/**
274
	 * @param string $path
275
	 * @param string $target
276
	 */
277
	private function addLocalFolder($path, $target) {
278
		$dh = $this->opendir($path);
279
		if (is_resource($dh)) {
280
			while (($file = readdir($dh)) !== false) {
281
				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
282
					if ($this->is_dir($path . '/' . $file)) {
283
						mkdir($target . '/' . $file);
284
						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
285
					} else {
286
						$tmp = $this->toTmpFile($path . '/' . $file);
287
						rename($tmp, $target . '/' . $file);
288
					}
289
				}
290
			}
291
		}
292
	}
293
294
	/**
295
	 * @param string $query
296
	 * @param string $dir
297
	 * @return array
298
	 */
299
	protected function searchInDir($query, $dir = '') {
300
		$files = [];
301
		$dh = $this->opendir($dir);
302
		if (is_resource($dh)) {
303
			while (($item = readdir($dh)) !== false) {
304
				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
305
					continue;
306
				}
307
				if (strstr(strtolower($item), strtolower($query)) !== false) {
308
					$files[] = $dir . '/' . $item;
309
				}
310
				if ($this->is_dir($dir . '/' . $item)) {
311
					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
312
				}
313
			}
314
		}
315
		closedir($dh);
316
		return $files;
317
	}
318
319
	/**
320
	 * check if a file or folder has been updated since $time
321
	 *
322
	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
323
	 * the mtime should always return false here. As a result storage implementations that always return false expect
324
	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
325
	 * ownClouds filesystem.
326
	 *
327
	 * @param string $path
328
	 * @param int $time
329
	 * @return bool
330
	 */
331
	public function hasUpdated($path, $time) {
332
		return $this->filemtime($path) > $time;
333
	}
334
335
	public function getCache($path = '', $storage = null) {
336
		if (!$storage) {
337
			$storage = $this;
338
		}
339
		if (!isset($storage->cache)) {
340
			$storage->cache = new Cache($storage);
341
		}
342
		return $storage->cache;
343
	}
344
345
	public function getScanner($path = '', $storage = null) {
346
		if (!$storage) {
347
			$storage = $this;
348
		}
349
		if (!isset($storage->scanner)) {
350
			$storage->scanner = new Scanner($storage);
351
		}
352
		return $storage->scanner;
353
	}
354
355
	public function getWatcher($path = '', $storage = null) {
356
		if (!$storage) {
357
			$storage = $this;
358
		}
359
		if (!isset($this->watcher)) {
360
			$this->watcher = new Watcher($storage);
361
			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated. ( Ignorable by Annotation )

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

361
			$globalPolicy = /** @scrutinizer ignore-deprecated */ \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
Loading history...
362
			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
363
		}
364
		return $this->watcher;
365
	}
366
367
	/**
368
	 * get a propagator instance for the cache
369
	 *
370
	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
0 ignored issues
show
Bug introduced by
The type OC\Files\Storage\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
371
	 * @return \OC\Files\Cache\Propagator
372
	 */
373
	public function getPropagator($storage = null) {
374
		if (!$storage) {
375
			$storage = $this;
376
		}
377
		if (!isset($storage->propagator)) {
378
			$config = \OC::$server->getSystemConfig();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getSystemConfig() has been deprecated. ( Ignorable by Annotation )

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

378
			$config = /** @scrutinizer ignore-deprecated */ \OC::$server->getSystemConfig();
Loading history...
379
			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getDatabaseConnection() has been deprecated. ( Ignorable by Annotation )

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

379
			$storage->propagator = new Propagator($storage, /** @scrutinizer ignore-deprecated */ \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
Loading history...
380
		}
381
		return $storage->propagator;
382
	}
383
384
	public function getUpdater($storage = null) {
385
		if (!$storage) {
386
			$storage = $this;
387
		}
388
		if (!isset($storage->updater)) {
389
			$storage->updater = new Updater($storage);
390
		}
391
		return $storage->updater;
392
	}
393
394
	public function getStorageCache($storage = null) {
395
		if (!$storage) {
396
			$storage = $this;
397
		}
398
		if (!isset($this->storageCache)) {
399
			$this->storageCache = new \OC\Files\Cache\Storage($storage);
400
		}
401
		return $this->storageCache;
402
	}
403
404
	/**
405
	 * get the owner of a path
406
	 *
407
	 * @param string $path The path to get the owner
408
	 * @return string|false uid or false
409
	 */
410
	public function getOwner($path) {
411
		if ($this->owner === null) {
412
			$this->owner = \OC_User::getUser();
413
		}
414
415
		return $this->owner;
416
	}
417
418
	/**
419
	 * get the ETag for a file or folder
420
	 *
421
	 * @param string $path
422
	 * @return string
423
	 */
424
	public function getETag($path) {
425
		return uniqid();
426
	}
427
428
	/**
429
	 * clean a path, i.e. remove all redundant '.' and '..'
430
	 * making sure that it can't point to higher than '/'
431
	 *
432
	 * @param string $path The path to clean
433
	 * @return string cleaned path
434
	 */
435
	public function cleanPath($path) {
436
		if (strlen($path) == 0 or $path[0] != '/') {
437
			$path = '/' . $path;
438
		}
439
440
		$output = [];
441
		foreach (explode('/', $path) as $chunk) {
442
			if ($chunk == '..') {
443
				array_pop($output);
444
			} elseif ($chunk == '.') {
445
			} else {
446
				$output[] = $chunk;
447
			}
448
		}
449
		return implode('/', $output);
450
	}
451
452
	/**
453
	 * Test a storage for availability
454
	 *
455
	 * @return bool
456
	 */
457
	public function test() {
458
		try {
459
			if ($this->stat('')) {
460
				return true;
461
			}
462
			\OC::$server->getLogger()->info("External storage not available: stat() failed");
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

462
			/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->info("External storage not available: stat() failed");
Loading history...
463
			return false;
464
		} catch (\Exception $e) {
465
			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

465
			/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
Loading history...
466
			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

466
			/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
Loading history...
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

466
			\OC::$server->getLogger()->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::WARN]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
467
			return false;
468
		}
469
	}
470
471
	/**
472
	 * get the free space in the storage
473
	 *
474
	 * @param string $path
475
	 * @return int|false
476
	 */
477
	public function free_space($path) {
478
		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
479
	}
480
481
	/**
482
	 * {@inheritdoc}
483
	 */
484
	public function isLocal() {
485
		// the common implementation returns a temporary file by
486
		// default, which is not local
487
		return false;
488
	}
489
490
	/**
491
	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
492
	 *
493
	 * @param string $class
494
	 * @return bool
495
	 */
496
	public function instanceOfStorage($class) {
497
		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
498
			// FIXME Temporary fix to keep existing checks working
499
			$class = '\OCA\Files_Sharing\SharedStorage';
500
		}
501
		return is_a($this, $class);
502
	}
503
504
	/**
505
	 * A custom storage implementation can return an url for direct download of a give file.
506
	 *
507
	 * For now the returned array can hold the parameter url - in future more attributes might follow.
508
	 *
509
	 * @param string $path
510
	 * @return array|false
511
	 */
512
	public function getDirectDownload($path) {
513
		return [];
514
	}
515
516
	/**
517
	 * @inheritdoc
518
	 * @throws InvalidPathException
519
	 */
520
	public function verifyPath($path, $fileName) {
521
522
		// verify empty and dot files
523
		$trimmed = trim($fileName);
524
		if ($trimmed === '') {
525
			throw new EmptyFileNameException();
526
		}
527
528
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
529
			throw new InvalidDirectoryException();
530
		}
531
532
		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getDatabaseConnection() has been deprecated. ( Ignorable by Annotation )

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

532
		if (!/** @scrutinizer ignore-deprecated */ \OC::$server->getDatabaseConnection()->supports4ByteText()) {
Loading history...
533
			// verify database - e.g. mysql only 3-byte chars
534
			if (preg_match('%(?:
535
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
536
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
537
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
538
)%xs', $fileName)) {
539
				throw new InvalidCharacterInPathException();
540
			}
541
		}
542
543
		// 255 characters is the limit on common file systems (ext/xfs)
544
		// oc_filecache has a 250 char length limit for the filename
545
		if (isset($fileName[250])) {
546
			throw new FileNameTooLongException();
547
		}
548
549
		// NOTE: $path will remain unverified for now
550
		$this->verifyPosixPath($fileName);
551
	}
552
553
	/**
554
	 * @param string $fileName
555
	 * @throws InvalidPathException
556
	 */
557
	protected function verifyPosixPath($fileName) {
558
		$fileName = trim($fileName);
559
		$this->scanForInvalidCharacters($fileName, "\\/");
560
		$reservedNames = ['*'];
561
		if (in_array($fileName, $reservedNames)) {
562
			throw new ReservedWordException();
563
		}
564
	}
565
566
	/**
567
	 * @param string $fileName
568
	 * @param string $invalidChars
569
	 * @throws InvalidPathException
570
	 */
571
	private function scanForInvalidCharacters($fileName, $invalidChars) {
572
		foreach (str_split($invalidChars) as $char) {
573
			if (strpos($fileName, $char) !== false) {
574
				throw new InvalidCharacterInPathException();
575
			}
576
		}
577
578
		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
579
		if ($sanitizedFileName !== $fileName) {
580
			throw new InvalidCharacterInPathException();
581
		}
582
	}
583
584
	/**
585
	 * @param array $options
586
	 */
587
	public function setMountOptions(array $options) {
588
		$this->mountOptions = $options;
589
	}
590
591
	/**
592
	 * @param string $name
593
	 * @param mixed $default
594
	 * @return mixed
595
	 */
596
	public function getMountOption($name, $default = null) {
597
		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
598
	}
599
600
	/**
601
	 * @param IStorage $sourceStorage
602
	 * @param string $sourceInternalPath
603
	 * @param string $targetInternalPath
604
	 * @param bool $preserveMtime
605
	 * @return bool
606
	 */
607
	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
608
		if ($sourceStorage === $this) {
0 ignored issues
show
introduced by
The condition $sourceStorage === $this is always false.
Loading history...
609
			return $this->copy($sourceInternalPath, $targetInternalPath);
610
		}
611
612
		if ($sourceStorage->is_dir($sourceInternalPath)) {
613
			$dh = $sourceStorage->opendir($sourceInternalPath);
614
			$result = $this->mkdir($targetInternalPath);
615
			if (is_resource($dh)) {
616
				while ($result and ($file = readdir($dh)) !== false) {
617
					if (!Filesystem::isIgnoredDir($file)) {
618
						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
619
					}
620
				}
621
			}
622
		} else {
623
			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
624
			$result = false;
625
			if ($source) {
0 ignored issues
show
introduced by
$source is of type false|resource, thus it always evaluated to false.
Loading history...
626
				try {
627
					$this->writeStream($targetInternalPath, $source);
628
					$result = true;
629
				} catch (\Exception $e) {
630
					\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
631
				}
632
			}
633
634
			if ($result and $preserveMtime) {
0 ignored issues
show
introduced by
The condition $result is always false.
Loading history...
635
				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
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]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

755
				$logger->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
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]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

787
				$logger->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
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]);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

818
			/** @scrutinizer ignore-deprecated */ \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
Loading history...
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

818
			\OC::$server->getLogger()->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated. ( Ignorable by Annotation )

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

825
			$this->shouldLogLocks = /** @scrutinizer ignore-deprecated */ \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
Loading history...
826
			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

826
			$this->logger = $this->shouldLogLocks ? /** @scrutinizer ignore-deprecated */ \OC::$server->getLogger() : null;
Loading history...
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) {
0 ignored issues
show
introduced by
$target is of type false|resource, thus it always evaluated to false.
Loading history...
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
}
893