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