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