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