Passed
Push — master ( f33305...caebdc )
by Robin
15:26 queued 12s
created
lib/private/Files/Storage/Local.php 1 patch
Indentation   +538 added lines, -538 removed lines patch added patch discarded remove patch
@@ -56,542 +56,542 @@
 block discarded – undo
56 56
  * for local filestore, we only have to map the paths
57 57
  */
58 58
 class Local extends \OC\Files\Storage\Common {
59
-	protected $datadir;
60
-
61
-	protected $dataDirLength;
62
-
63
-	protected $realDataDir;
64
-
65
-	private IConfig $config;
66
-
67
-	private IMimeTypeDetector $mimeTypeDetector;
68
-
69
-	public function __construct($arguments) {
70
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
71
-			throw new \InvalidArgumentException('No data directory set for local storage');
72
-		}
73
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
74
-		// some crazy code uses a local storage on root...
75
-		if ($this->datadir === '/') {
76
-			$this->realDataDir = $this->datadir;
77
-		} else {
78
-			$realPath = realpath($this->datadir) ?: $this->datadir;
79
-			$this->realDataDir = rtrim($realPath, '/') . '/';
80
-		}
81
-		if (substr($this->datadir, -1) !== '/') {
82
-			$this->datadir .= '/';
83
-		}
84
-		$this->dataDirLength = strlen($this->realDataDir);
85
-		$this->config = \OC::$server->get(IConfig::class);
86
-		$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
87
-	}
88
-
89
-	public function __destruct() {
90
-	}
91
-
92
-	public function getId() {
93
-		return 'local::' . $this->datadir;
94
-	}
95
-
96
-	public function mkdir($path) {
97
-		$sourcePath = $this->getSourcePath($path);
98
-		$oldMask = umask(022);
99
-		$result = @mkdir($sourcePath, 0777, true);
100
-		umask($oldMask);
101
-		return $result;
102
-	}
103
-
104
-	public function rmdir($path) {
105
-		if (!$this->isDeletable($path)) {
106
-			return false;
107
-		}
108
-		try {
109
-			$it = new \RecursiveIteratorIterator(
110
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
111
-				\RecursiveIteratorIterator::CHILD_FIRST
112
-			);
113
-			/**
114
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
115
-			 * This bug is fixed in PHP 5.5.9 or before
116
-			 * See #8376
117
-			 */
118
-			$it->rewind();
119
-			while ($it->valid()) {
120
-				/**
121
-				 * @var \SplFileInfo $file
122
-				 */
123
-				$file = $it->current();
124
-				clearstatcache(true, $this->getSourcePath($file));
125
-				if (in_array($file->getBasename(), ['.', '..'])) {
126
-					$it->next();
127
-					continue;
128
-				} elseif ($file->isDir()) {
129
-					rmdir($file->getPathname());
130
-				} elseif ($file->isFile() || $file->isLink()) {
131
-					unlink($file->getPathname());
132
-				}
133
-				$it->next();
134
-			}
135
-			clearstatcache(true, $this->getSourcePath($path));
136
-			return rmdir($this->getSourcePath($path));
137
-		} catch (\UnexpectedValueException $e) {
138
-			return false;
139
-		}
140
-	}
141
-
142
-	public function opendir($path) {
143
-		return opendir($this->getSourcePath($path));
144
-	}
145
-
146
-	public function is_dir($path) {
147
-		if (substr($path, -1) == '/') {
148
-			$path = substr($path, 0, -1);
149
-		}
150
-		return is_dir($this->getSourcePath($path));
151
-	}
152
-
153
-	public function is_file($path) {
154
-		return is_file($this->getSourcePath($path));
155
-	}
156
-
157
-	public function stat($path) {
158
-		$fullPath = $this->getSourcePath($path);
159
-		clearstatcache(true, $fullPath);
160
-		$statResult = @stat($fullPath);
161
-		if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) {
162
-			$filesize = $this->filesize($path);
163
-			$statResult['size'] = $filesize;
164
-			$statResult[7] = $filesize;
165
-		}
166
-		if (is_array($statResult)) {
167
-			$statResult['full_path'] = $fullPath;
168
-		}
169
-		return $statResult;
170
-	}
171
-
172
-	/**
173
-	 * @inheritdoc
174
-	 */
175
-	public function getMetaData($path) {
176
-		try {
177
-			$stat = $this->stat($path);
178
-		} catch (ForbiddenException $e) {
179
-			return null;
180
-		}
181
-		if (!$stat) {
182
-			return null;
183
-		}
184
-
185
-		$permissions = Constants::PERMISSION_SHARE;
186
-		$statPermissions = $stat['mode'];
187
-		$isDir = ($statPermissions & 0x4000) === 0x4000 && !($statPermissions & 0x8000);
188
-		if ($statPermissions & 0x0100) {
189
-			$permissions += Constants::PERMISSION_READ;
190
-		}
191
-		if ($statPermissions & 0x0080) {
192
-			$permissions += Constants::PERMISSION_UPDATE;
193
-			if ($isDir) {
194
-				$permissions += Constants::PERMISSION_CREATE;
195
-			}
196
-		}
197
-
198
-		if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
199
-			$parent = dirname($stat['full_path']);
200
-			if (is_writable($parent)) {
201
-				$permissions += Constants::PERMISSION_DELETE;
202
-			}
203
-		}
204
-
205
-		$data = [];
206
-		$data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path);
207
-		$data['mtime'] = $stat['mtime'];
208
-		if ($data['mtime'] === false) {
209
-			$data['mtime'] = time();
210
-		}
211
-		if ($isDir) {
212
-			$data['size'] = -1; //unknown
213
-		} else {
214
-			$data['size'] = $stat['size'];
215
-		}
216
-		$data['etag'] = $this->calculateEtag($path, $stat);
217
-		$data['storage_mtime'] = $data['mtime'];
218
-		$data['permissions'] = $permissions;
219
-		$data['name'] = basename($path);
220
-
221
-		return $data;
222
-	}
223
-
224
-	public function filetype($path) {
225
-		$filetype = filetype($this->getSourcePath($path));
226
-		if ($filetype == 'link') {
227
-			$filetype = filetype(realpath($this->getSourcePath($path)));
228
-		}
229
-		return $filetype;
230
-	}
231
-
232
-	public function filesize($path) {
233
-		if (!$this->is_file($path)) {
234
-			return 0;
235
-		}
236
-		$fullPath = $this->getSourcePath($path);
237
-		if (PHP_INT_SIZE === 4) {
238
-			$helper = new \OC\LargeFileHelper;
239
-			return $helper->getFileSize($fullPath);
240
-		}
241
-		return filesize($fullPath);
242
-	}
243
-
244
-	public function isReadable($path) {
245
-		return is_readable($this->getSourcePath($path));
246
-	}
247
-
248
-	public function isUpdatable($path) {
249
-		return is_writable($this->getSourcePath($path));
250
-	}
251
-
252
-	public function file_exists($path) {
253
-		return file_exists($this->getSourcePath($path));
254
-	}
255
-
256
-	public function filemtime($path) {
257
-		$fullPath = $this->getSourcePath($path);
258
-		clearstatcache(true, $fullPath);
259
-		if (!$this->file_exists($path)) {
260
-			return false;
261
-		}
262
-		if (PHP_INT_SIZE === 4) {
263
-			$helper = new \OC\LargeFileHelper();
264
-			return $helper->getFileMtime($fullPath);
265
-		}
266
-		return filemtime($fullPath);
267
-	}
268
-
269
-	public function touch($path, $mtime = null) {
270
-		// sets the modification time of the file to the given value.
271
-		// If mtime is nil the current time is set.
272
-		// note that the access time of the file always changes to the current time.
273
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
274
-			return false;
275
-		}
276
-		$oldMask = umask(022);
277
-		if (!is_null($mtime)) {
278
-			$result = @touch($this->getSourcePath($path), $mtime);
279
-		} else {
280
-			$result = @touch($this->getSourcePath($path));
281
-		}
282
-		umask($oldMask);
283
-		if ($result) {
284
-			clearstatcache(true, $this->getSourcePath($path));
285
-		}
286
-
287
-		return $result;
288
-	}
289
-
290
-	public function file_get_contents($path) {
291
-		return file_get_contents($this->getSourcePath($path));
292
-	}
293
-
294
-	public function file_put_contents($path, $data) {
295
-		$oldMask = umask(022);
296
-		$result = file_put_contents($this->getSourcePath($path), $data);
297
-		umask($oldMask);
298
-		return $result;
299
-	}
300
-
301
-	public function unlink($path) {
302
-		if ($this->is_dir($path)) {
303
-			return $this->rmdir($path);
304
-		} elseif ($this->is_file($path)) {
305
-			return unlink($this->getSourcePath($path));
306
-		} else {
307
-			return false;
308
-		}
309
-	}
310
-
311
-	private function checkTreeForForbiddenItems(string $path) {
312
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
313
-		foreach ($iterator as $file) {
314
-			/** @var \SplFileInfo $file */
315
-			if (Filesystem::isFileBlacklisted($file->getBasename())) {
316
-				throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false);
317
-			}
318
-		}
319
-	}
320
-
321
-	public function rename($path1, $path2) {
322
-		$srcParent = dirname($path1);
323
-		$dstParent = dirname($path2);
324
-
325
-		if (!$this->isUpdatable($srcParent)) {
326
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
327
-			return false;
328
-		}
329
-
330
-		if (!$this->isUpdatable($dstParent)) {
331
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
332
-			return false;
333
-		}
334
-
335
-		if (!$this->file_exists($path1)) {
336
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
337
-			return false;
338
-		}
339
-
340
-		if ($this->is_dir($path2)) {
341
-			$this->rmdir($path2);
342
-		} elseif ($this->is_file($path2)) {
343
-			$this->unlink($path2);
344
-		}
345
-
346
-		if ($this->is_dir($path1)) {
347
-			// we can't move folders across devices, use copy instead
348
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
349
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
350
-			if ($stat1['dev'] !== $stat2['dev']) {
351
-				$result = $this->copy($path1, $path2);
352
-				if ($result) {
353
-					$result &= $this->rmdir($path1);
354
-				}
355
-				return $result;
356
-			}
357
-
358
-			$this->checkTreeForForbiddenItems($this->getSourcePath($path1));
359
-		}
360
-
361
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
362
-	}
363
-
364
-	public function copy($path1, $path2) {
365
-		if ($this->is_dir($path1)) {
366
-			return parent::copy($path1, $path2);
367
-		} else {
368
-			$oldMask = umask(022);
369
-			$result = copy($this->getSourcePath($path1), $this->getSourcePath($path2));
370
-			umask($oldMask);
371
-			return $result;
372
-		}
373
-	}
374
-
375
-	public function fopen($path, $mode) {
376
-		$oldMask = umask(022);
377
-		$result = fopen($this->getSourcePath($path), $mode);
378
-		umask($oldMask);
379
-		return $result;
380
-	}
381
-
382
-	public function hash($type, $path, $raw = false) {
383
-		return hash_file($type, $this->getSourcePath($path), $raw);
384
-	}
385
-
386
-	public function free_space($path) {
387
-		$sourcePath = $this->getSourcePath($path);
388
-		// using !is_dir because $sourcePath might be a part file or
389
-		// non-existing file, so we'd still want to use the parent dir
390
-		// in such cases
391
-		if (!is_dir($sourcePath)) {
392
-			// disk_free_space doesn't work on files
393
-			$sourcePath = dirname($sourcePath);
394
-		}
395
-		$space = function_exists('disk_free_space') ? disk_free_space($sourcePath) : false;
396
-		if ($space === false || is_null($space)) {
397
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
398
-		}
399
-		return $space;
400
-	}
401
-
402
-	public function search($query) {
403
-		return $this->searchInDir($query);
404
-	}
405
-
406
-	public function getLocalFile($path) {
407
-		return $this->getSourcePath($path);
408
-	}
409
-
410
-	public function getLocalFolder($path) {
411
-		return $this->getSourcePath($path);
412
-	}
413
-
414
-	/**
415
-	 * @param string $query
416
-	 * @param string $dir
417
-	 * @return array
418
-	 */
419
-	protected function searchInDir($query, $dir = '') {
420
-		$files = [];
421
-		$physicalDir = $this->getSourcePath($dir);
422
-		foreach (scandir($physicalDir) as $item) {
423
-			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
424
-				continue;
425
-			}
426
-			$physicalItem = $physicalDir . '/' . $item;
427
-
428
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
429
-				$files[] = $dir . '/' . $item;
430
-			}
431
-			if (is_dir($physicalItem)) {
432
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
433
-			}
434
-		}
435
-		return $files;
436
-	}
437
-
438
-	/**
439
-	 * check if a file or folder has been updated since $time
440
-	 *
441
-	 * @param string $path
442
-	 * @param int $time
443
-	 * @return bool
444
-	 */
445
-	public function hasUpdated($path, $time) {
446
-		if ($this->file_exists($path)) {
447
-			return $this->filemtime($path) > $time;
448
-		} else {
449
-			return true;
450
-		}
451
-	}
452
-
453
-	/**
454
-	 * Get the source path (on disk) of a given path
455
-	 *
456
-	 * @param string $path
457
-	 * @return string
458
-	 * @throws ForbiddenException
459
-	 */
460
-	public function getSourcePath($path) {
461
-		if (Filesystem::isFileBlacklisted($path)) {
462
-			throw new ForbiddenException('Invalid path: ' . $path, false);
463
-		}
464
-
465
-		$fullPath = $this->datadir . $path;
466
-		$currentPath = $path;
467
-		$allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false);
468
-		if ($allowSymlinks || $currentPath === '') {
469
-			return $fullPath;
470
-		}
471
-		$pathToResolve = $fullPath;
472
-		$realPath = realpath($pathToResolve);
473
-		while ($realPath === false) { // for non existing files check the parent directory
474
-			$currentPath = dirname($currentPath);
475
-			if ($currentPath === '' || $currentPath === '.') {
476
-				return $fullPath;
477
-			}
478
-			$realPath = realpath($this->datadir . $currentPath);
479
-		}
480
-		if ($realPath) {
481
-			$realPath = $realPath . '/';
482
-		}
483
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
484
-			return $fullPath;
485
-		}
486
-
487
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
488
-		throw new ForbiddenException('Following symlinks is not allowed', false);
489
-	}
490
-
491
-	/**
492
-	 * {@inheritdoc}
493
-	 */
494
-	public function isLocal() {
495
-		return true;
496
-	}
497
-
498
-	/**
499
-	 * get the ETag for a file or folder
500
-	 *
501
-	 * @param string $path
502
-	 * @return string
503
-	 */
504
-	public function getETag($path) {
505
-		return $this->calculateEtag($path, $this->stat($path));
506
-	}
507
-
508
-	private function calculateEtag(string $path, array $stat): string {
509
-		if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket
510
-			return parent::getETag($path);
511
-		} else {
512
-			if ($stat === false) {
513
-				return md5('');
514
-			}
515
-
516
-			$toHash = '';
517
-			if (isset($stat['mtime'])) {
518
-				$toHash .= $stat['mtime'];
519
-			}
520
-			if (isset($stat['ino'])) {
521
-				$toHash .= $stat['ino'];
522
-			}
523
-			if (isset($stat['dev'])) {
524
-				$toHash .= $stat['dev'];
525
-			}
526
-			if (isset($stat['size'])) {
527
-				$toHash .= $stat['size'];
528
-			}
529
-
530
-			return md5($toHash);
531
-		}
532
-	}
533
-
534
-	/**
535
-	 * @param IStorage $sourceStorage
536
-	 * @param string $sourceInternalPath
537
-	 * @param string $targetInternalPath
538
-	 * @param bool $preserveMtime
539
-	 * @return bool
540
-	 */
541
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
542
-		// Don't treat ACLStorageWrapper like local storage where copy can be done directly.
543
-		// Instead use the slower recursive copying in php from Common::copyFromStorage with
544
-		// more permissions checks.
545
-		if ($sourceStorage->instanceOfStorage(Local::class) && !$sourceStorage->instanceOfStorage('OCA\GroupFolders\ACL\ACLStorageWrapper')) {
546
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
547
-				/**
548
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
549
-				 */
550
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
551
-			}
552
-			/**
553
-			 * @var \OC\Files\Storage\Local $sourceStorage
554
-			 */
555
-			$rootStorage = new Local(['datadir' => '/']);
556
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
557
-		} else {
558
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
559
-		}
560
-	}
561
-
562
-	/**
563
-	 * @param IStorage $sourceStorage
564
-	 * @param string $sourceInternalPath
565
-	 * @param string $targetInternalPath
566
-	 * @return bool
567
-	 */
568
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
569
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
570
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
571
-				/**
572
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
573
-				 */
574
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
575
-			}
576
-			/**
577
-			 * @var \OC\Files\Storage\Local $sourceStorage
578
-			 */
579
-			$rootStorage = new Local(['datadir' => '/']);
580
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
581
-		} else {
582
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
583
-		}
584
-	}
585
-
586
-	public function writeStream(string $path, $stream, int $size = null): int {
587
-		$result = $this->file_put_contents($path, $stream);
588
-		if (is_resource($stream)) {
589
-			fclose($stream);
590
-		}
591
-		if ($result === false) {
592
-			throw new GenericFileException("Failed write stream to $path");
593
-		} else {
594
-			return $result;
595
-		}
596
-	}
59
+    protected $datadir;
60
+
61
+    protected $dataDirLength;
62
+
63
+    protected $realDataDir;
64
+
65
+    private IConfig $config;
66
+
67
+    private IMimeTypeDetector $mimeTypeDetector;
68
+
69
+    public function __construct($arguments) {
70
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
71
+            throw new \InvalidArgumentException('No data directory set for local storage');
72
+        }
73
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
74
+        // some crazy code uses a local storage on root...
75
+        if ($this->datadir === '/') {
76
+            $this->realDataDir = $this->datadir;
77
+        } else {
78
+            $realPath = realpath($this->datadir) ?: $this->datadir;
79
+            $this->realDataDir = rtrim($realPath, '/') . '/';
80
+        }
81
+        if (substr($this->datadir, -1) !== '/') {
82
+            $this->datadir .= '/';
83
+        }
84
+        $this->dataDirLength = strlen($this->realDataDir);
85
+        $this->config = \OC::$server->get(IConfig::class);
86
+        $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
87
+    }
88
+
89
+    public function __destruct() {
90
+    }
91
+
92
+    public function getId() {
93
+        return 'local::' . $this->datadir;
94
+    }
95
+
96
+    public function mkdir($path) {
97
+        $sourcePath = $this->getSourcePath($path);
98
+        $oldMask = umask(022);
99
+        $result = @mkdir($sourcePath, 0777, true);
100
+        umask($oldMask);
101
+        return $result;
102
+    }
103
+
104
+    public function rmdir($path) {
105
+        if (!$this->isDeletable($path)) {
106
+            return false;
107
+        }
108
+        try {
109
+            $it = new \RecursiveIteratorIterator(
110
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
111
+                \RecursiveIteratorIterator::CHILD_FIRST
112
+            );
113
+            /**
114
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
115
+             * This bug is fixed in PHP 5.5.9 or before
116
+             * See #8376
117
+             */
118
+            $it->rewind();
119
+            while ($it->valid()) {
120
+                /**
121
+                 * @var \SplFileInfo $file
122
+                 */
123
+                $file = $it->current();
124
+                clearstatcache(true, $this->getSourcePath($file));
125
+                if (in_array($file->getBasename(), ['.', '..'])) {
126
+                    $it->next();
127
+                    continue;
128
+                } elseif ($file->isDir()) {
129
+                    rmdir($file->getPathname());
130
+                } elseif ($file->isFile() || $file->isLink()) {
131
+                    unlink($file->getPathname());
132
+                }
133
+                $it->next();
134
+            }
135
+            clearstatcache(true, $this->getSourcePath($path));
136
+            return rmdir($this->getSourcePath($path));
137
+        } catch (\UnexpectedValueException $e) {
138
+            return false;
139
+        }
140
+    }
141
+
142
+    public function opendir($path) {
143
+        return opendir($this->getSourcePath($path));
144
+    }
145
+
146
+    public function is_dir($path) {
147
+        if (substr($path, -1) == '/') {
148
+            $path = substr($path, 0, -1);
149
+        }
150
+        return is_dir($this->getSourcePath($path));
151
+    }
152
+
153
+    public function is_file($path) {
154
+        return is_file($this->getSourcePath($path));
155
+    }
156
+
157
+    public function stat($path) {
158
+        $fullPath = $this->getSourcePath($path);
159
+        clearstatcache(true, $fullPath);
160
+        $statResult = @stat($fullPath);
161
+        if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) {
162
+            $filesize = $this->filesize($path);
163
+            $statResult['size'] = $filesize;
164
+            $statResult[7] = $filesize;
165
+        }
166
+        if (is_array($statResult)) {
167
+            $statResult['full_path'] = $fullPath;
168
+        }
169
+        return $statResult;
170
+    }
171
+
172
+    /**
173
+     * @inheritdoc
174
+     */
175
+    public function getMetaData($path) {
176
+        try {
177
+            $stat = $this->stat($path);
178
+        } catch (ForbiddenException $e) {
179
+            return null;
180
+        }
181
+        if (!$stat) {
182
+            return null;
183
+        }
184
+
185
+        $permissions = Constants::PERMISSION_SHARE;
186
+        $statPermissions = $stat['mode'];
187
+        $isDir = ($statPermissions & 0x4000) === 0x4000 && !($statPermissions & 0x8000);
188
+        if ($statPermissions & 0x0100) {
189
+            $permissions += Constants::PERMISSION_READ;
190
+        }
191
+        if ($statPermissions & 0x0080) {
192
+            $permissions += Constants::PERMISSION_UPDATE;
193
+            if ($isDir) {
194
+                $permissions += Constants::PERMISSION_CREATE;
195
+            }
196
+        }
197
+
198
+        if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions
199
+            $parent = dirname($stat['full_path']);
200
+            if (is_writable($parent)) {
201
+                $permissions += Constants::PERMISSION_DELETE;
202
+            }
203
+        }
204
+
205
+        $data = [];
206
+        $data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path);
207
+        $data['mtime'] = $stat['mtime'];
208
+        if ($data['mtime'] === false) {
209
+            $data['mtime'] = time();
210
+        }
211
+        if ($isDir) {
212
+            $data['size'] = -1; //unknown
213
+        } else {
214
+            $data['size'] = $stat['size'];
215
+        }
216
+        $data['etag'] = $this->calculateEtag($path, $stat);
217
+        $data['storage_mtime'] = $data['mtime'];
218
+        $data['permissions'] = $permissions;
219
+        $data['name'] = basename($path);
220
+
221
+        return $data;
222
+    }
223
+
224
+    public function filetype($path) {
225
+        $filetype = filetype($this->getSourcePath($path));
226
+        if ($filetype == 'link') {
227
+            $filetype = filetype(realpath($this->getSourcePath($path)));
228
+        }
229
+        return $filetype;
230
+    }
231
+
232
+    public function filesize($path) {
233
+        if (!$this->is_file($path)) {
234
+            return 0;
235
+        }
236
+        $fullPath = $this->getSourcePath($path);
237
+        if (PHP_INT_SIZE === 4) {
238
+            $helper = new \OC\LargeFileHelper;
239
+            return $helper->getFileSize($fullPath);
240
+        }
241
+        return filesize($fullPath);
242
+    }
243
+
244
+    public function isReadable($path) {
245
+        return is_readable($this->getSourcePath($path));
246
+    }
247
+
248
+    public function isUpdatable($path) {
249
+        return is_writable($this->getSourcePath($path));
250
+    }
251
+
252
+    public function file_exists($path) {
253
+        return file_exists($this->getSourcePath($path));
254
+    }
255
+
256
+    public function filemtime($path) {
257
+        $fullPath = $this->getSourcePath($path);
258
+        clearstatcache(true, $fullPath);
259
+        if (!$this->file_exists($path)) {
260
+            return false;
261
+        }
262
+        if (PHP_INT_SIZE === 4) {
263
+            $helper = new \OC\LargeFileHelper();
264
+            return $helper->getFileMtime($fullPath);
265
+        }
266
+        return filemtime($fullPath);
267
+    }
268
+
269
+    public function touch($path, $mtime = null) {
270
+        // sets the modification time of the file to the given value.
271
+        // If mtime is nil the current time is set.
272
+        // note that the access time of the file always changes to the current time.
273
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
274
+            return false;
275
+        }
276
+        $oldMask = umask(022);
277
+        if (!is_null($mtime)) {
278
+            $result = @touch($this->getSourcePath($path), $mtime);
279
+        } else {
280
+            $result = @touch($this->getSourcePath($path));
281
+        }
282
+        umask($oldMask);
283
+        if ($result) {
284
+            clearstatcache(true, $this->getSourcePath($path));
285
+        }
286
+
287
+        return $result;
288
+    }
289
+
290
+    public function file_get_contents($path) {
291
+        return file_get_contents($this->getSourcePath($path));
292
+    }
293
+
294
+    public function file_put_contents($path, $data) {
295
+        $oldMask = umask(022);
296
+        $result = file_put_contents($this->getSourcePath($path), $data);
297
+        umask($oldMask);
298
+        return $result;
299
+    }
300
+
301
+    public function unlink($path) {
302
+        if ($this->is_dir($path)) {
303
+            return $this->rmdir($path);
304
+        } elseif ($this->is_file($path)) {
305
+            return unlink($this->getSourcePath($path));
306
+        } else {
307
+            return false;
308
+        }
309
+    }
310
+
311
+    private function checkTreeForForbiddenItems(string $path) {
312
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
313
+        foreach ($iterator as $file) {
314
+            /** @var \SplFileInfo $file */
315
+            if (Filesystem::isFileBlacklisted($file->getBasename())) {
316
+                throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false);
317
+            }
318
+        }
319
+    }
320
+
321
+    public function rename($path1, $path2) {
322
+        $srcParent = dirname($path1);
323
+        $dstParent = dirname($path2);
324
+
325
+        if (!$this->isUpdatable($srcParent)) {
326
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
327
+            return false;
328
+        }
329
+
330
+        if (!$this->isUpdatable($dstParent)) {
331
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
332
+            return false;
333
+        }
334
+
335
+        if (!$this->file_exists($path1)) {
336
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
337
+            return false;
338
+        }
339
+
340
+        if ($this->is_dir($path2)) {
341
+            $this->rmdir($path2);
342
+        } elseif ($this->is_file($path2)) {
343
+            $this->unlink($path2);
344
+        }
345
+
346
+        if ($this->is_dir($path1)) {
347
+            // we can't move folders across devices, use copy instead
348
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
349
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
350
+            if ($stat1['dev'] !== $stat2['dev']) {
351
+                $result = $this->copy($path1, $path2);
352
+                if ($result) {
353
+                    $result &= $this->rmdir($path1);
354
+                }
355
+                return $result;
356
+            }
357
+
358
+            $this->checkTreeForForbiddenItems($this->getSourcePath($path1));
359
+        }
360
+
361
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
362
+    }
363
+
364
+    public function copy($path1, $path2) {
365
+        if ($this->is_dir($path1)) {
366
+            return parent::copy($path1, $path2);
367
+        } else {
368
+            $oldMask = umask(022);
369
+            $result = copy($this->getSourcePath($path1), $this->getSourcePath($path2));
370
+            umask($oldMask);
371
+            return $result;
372
+        }
373
+    }
374
+
375
+    public function fopen($path, $mode) {
376
+        $oldMask = umask(022);
377
+        $result = fopen($this->getSourcePath($path), $mode);
378
+        umask($oldMask);
379
+        return $result;
380
+    }
381
+
382
+    public function hash($type, $path, $raw = false) {
383
+        return hash_file($type, $this->getSourcePath($path), $raw);
384
+    }
385
+
386
+    public function free_space($path) {
387
+        $sourcePath = $this->getSourcePath($path);
388
+        // using !is_dir because $sourcePath might be a part file or
389
+        // non-existing file, so we'd still want to use the parent dir
390
+        // in such cases
391
+        if (!is_dir($sourcePath)) {
392
+            // disk_free_space doesn't work on files
393
+            $sourcePath = dirname($sourcePath);
394
+        }
395
+        $space = function_exists('disk_free_space') ? disk_free_space($sourcePath) : false;
396
+        if ($space === false || is_null($space)) {
397
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
398
+        }
399
+        return $space;
400
+    }
401
+
402
+    public function search($query) {
403
+        return $this->searchInDir($query);
404
+    }
405
+
406
+    public function getLocalFile($path) {
407
+        return $this->getSourcePath($path);
408
+    }
409
+
410
+    public function getLocalFolder($path) {
411
+        return $this->getSourcePath($path);
412
+    }
413
+
414
+    /**
415
+     * @param string $query
416
+     * @param string $dir
417
+     * @return array
418
+     */
419
+    protected function searchInDir($query, $dir = '') {
420
+        $files = [];
421
+        $physicalDir = $this->getSourcePath($dir);
422
+        foreach (scandir($physicalDir) as $item) {
423
+            if (\OC\Files\Filesystem::isIgnoredDir($item)) {
424
+                continue;
425
+            }
426
+            $physicalItem = $physicalDir . '/' . $item;
427
+
428
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
429
+                $files[] = $dir . '/' . $item;
430
+            }
431
+            if (is_dir($physicalItem)) {
432
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
433
+            }
434
+        }
435
+        return $files;
436
+    }
437
+
438
+    /**
439
+     * check if a file or folder has been updated since $time
440
+     *
441
+     * @param string $path
442
+     * @param int $time
443
+     * @return bool
444
+     */
445
+    public function hasUpdated($path, $time) {
446
+        if ($this->file_exists($path)) {
447
+            return $this->filemtime($path) > $time;
448
+        } else {
449
+            return true;
450
+        }
451
+    }
452
+
453
+    /**
454
+     * Get the source path (on disk) of a given path
455
+     *
456
+     * @param string $path
457
+     * @return string
458
+     * @throws ForbiddenException
459
+     */
460
+    public function getSourcePath($path) {
461
+        if (Filesystem::isFileBlacklisted($path)) {
462
+            throw new ForbiddenException('Invalid path: ' . $path, false);
463
+        }
464
+
465
+        $fullPath = $this->datadir . $path;
466
+        $currentPath = $path;
467
+        $allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false);
468
+        if ($allowSymlinks || $currentPath === '') {
469
+            return $fullPath;
470
+        }
471
+        $pathToResolve = $fullPath;
472
+        $realPath = realpath($pathToResolve);
473
+        while ($realPath === false) { // for non existing files check the parent directory
474
+            $currentPath = dirname($currentPath);
475
+            if ($currentPath === '' || $currentPath === '.') {
476
+                return $fullPath;
477
+            }
478
+            $realPath = realpath($this->datadir . $currentPath);
479
+        }
480
+        if ($realPath) {
481
+            $realPath = $realPath . '/';
482
+        }
483
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
484
+            return $fullPath;
485
+        }
486
+
487
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
488
+        throw new ForbiddenException('Following symlinks is not allowed', false);
489
+    }
490
+
491
+    /**
492
+     * {@inheritdoc}
493
+     */
494
+    public function isLocal() {
495
+        return true;
496
+    }
497
+
498
+    /**
499
+     * get the ETag for a file or folder
500
+     *
501
+     * @param string $path
502
+     * @return string
503
+     */
504
+    public function getETag($path) {
505
+        return $this->calculateEtag($path, $this->stat($path));
506
+    }
507
+
508
+    private function calculateEtag(string $path, array $stat): string {
509
+        if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket
510
+            return parent::getETag($path);
511
+        } else {
512
+            if ($stat === false) {
513
+                return md5('');
514
+            }
515
+
516
+            $toHash = '';
517
+            if (isset($stat['mtime'])) {
518
+                $toHash .= $stat['mtime'];
519
+            }
520
+            if (isset($stat['ino'])) {
521
+                $toHash .= $stat['ino'];
522
+            }
523
+            if (isset($stat['dev'])) {
524
+                $toHash .= $stat['dev'];
525
+            }
526
+            if (isset($stat['size'])) {
527
+                $toHash .= $stat['size'];
528
+            }
529
+
530
+            return md5($toHash);
531
+        }
532
+    }
533
+
534
+    /**
535
+     * @param IStorage $sourceStorage
536
+     * @param string $sourceInternalPath
537
+     * @param string $targetInternalPath
538
+     * @param bool $preserveMtime
539
+     * @return bool
540
+     */
541
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
542
+        // Don't treat ACLStorageWrapper like local storage where copy can be done directly.
543
+        // Instead use the slower recursive copying in php from Common::copyFromStorage with
544
+        // more permissions checks.
545
+        if ($sourceStorage->instanceOfStorage(Local::class) && !$sourceStorage->instanceOfStorage('OCA\GroupFolders\ACL\ACLStorageWrapper')) {
546
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
547
+                /**
548
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
549
+                 */
550
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
551
+            }
552
+            /**
553
+             * @var \OC\Files\Storage\Local $sourceStorage
554
+             */
555
+            $rootStorage = new Local(['datadir' => '/']);
556
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
557
+        } else {
558
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
559
+        }
560
+    }
561
+
562
+    /**
563
+     * @param IStorage $sourceStorage
564
+     * @param string $sourceInternalPath
565
+     * @param string $targetInternalPath
566
+     * @return bool
567
+     */
568
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
569
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
570
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
571
+                /**
572
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
573
+                 */
574
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
575
+            }
576
+            /**
577
+             * @var \OC\Files\Storage\Local $sourceStorage
578
+             */
579
+            $rootStorage = new Local(['datadir' => '/']);
580
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
581
+        } else {
582
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
583
+        }
584
+    }
585
+
586
+    public function writeStream(string $path, $stream, int $size = null): int {
587
+        $result = $this->file_put_contents($path, $stream);
588
+        if (is_resource($stream)) {
589
+            fclose($stream);
590
+        }
591
+        if ($result === false) {
592
+            throw new GenericFileException("Failed write stream to $path");
593
+        } else {
594
+            return $result;
595
+        }
596
+    }
597 597
 }
Please login to merge, or discard this patch.