Completed
Push — master ( 63353a...ee8a58 )
by Sujith
09:16
created

Local::filesize()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Boris Rybalkin <[email protected]>
5
 * @author Brice Maron <[email protected]>
6
 * @author Jakob Sack <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Klaas Freitag <[email protected]>
10
 * @author Martin Mattel <[email protected]>
11
 * @author Michael Gapczynski <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Sjors van der Pluijm <[email protected]>
15
 * @author Stefan Weil <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Tigran Mkrtchyan <[email protected]>
18
 * @author Vincent Petry <[email protected]>
19
 *
20
 * @copyright Copyright (c) 2018, ownCloud GmbH
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
37
namespace OC\Files\Storage;
38
39
use OCP\Files\ForbiddenException;
40
41
/**
42
 * for local filestore, we only have to map the paths
43
 */
44
class Local extends Common {
45
	protected $datadir;
46
47
	protected $dataDirLength;
48
49
	protected $allowSymlinks = false;
50
51
	protected $realDataDir;
52
53
	public function __construct($arguments) {
54
		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
55
			throw new \InvalidArgumentException('No data directory set for local storage');
56
		}
57
		$this->datadir = $arguments['datadir'];
58
		// some crazy code uses a local storage on root...
59
		if ($this->datadir === '/') {
60
			$this->realDataDir = $this->datadir;
61
		} else {
62
			$this->realDataDir = rtrim(realpath($this->datadir), '/') . '/';
63
		}
64
		if (substr($this->datadir, -1) !== '/') {
65
			$this->datadir .= '/';
66
		}
67
		$this->dataDirLength = strlen($this->realDataDir);
68
	}
69
70
	public function __destruct() {
71
	}
72
73
	public function getId() {
74
		return 'local::' . $this->datadir;
75
	}
76
77
	public function mkdir($path) {
78
		return @mkdir($this->getSourcePath($path), 0777, true);
79
	}
80
81
	public function rmdir($path) {
82
		if (!$this->isDeletable($path)) {
83
			return false;
84
		}
85
		try {
86
			$it = new \RecursiveIteratorIterator(
87
				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
88
				\RecursiveIteratorIterator::CHILD_FIRST
89
			);
90
			/**
91
			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
92
			 * This bug is fixed in PHP 5.5.9 or before
93
			 * See #8376
94
			 */
95
			$it->rewind();
96
			while ($it->valid()) {
97
				/**
98
				 * @var \SplFileInfo $file
99
				 */
100
				$file = $it->current();
101
				if (in_array($file->getBasename(), ['.', '..'])) {
102
					$it->next();
103
					continue;
104
				} elseif ($file->isDir()) {
105
					rmdir($file->getPathname());
106
				} elseif ($file->isFile() || $file->isLink()) {
107
					unlink($file->getPathname());
108
				}
109
				$it->next();
110
			}
111
			return rmdir($this->getSourcePath($path));
112
		} catch (\UnexpectedValueException $e) {
113
			return false;
114
		}
115
	}
116
117
	public function opendir($path) {
118
		return opendir($this->getSourcePath($path));
119
	}
120
121
	public function is_dir($path) {
122
		if (substr($path, -1) == '/') {
123
			$path = substr($path, 0, -1);
124
		}
125
		return is_dir($this->getSourcePath($path));
126
	}
127
128
	public function is_file($path) {
129
		return is_file($this->getSourcePath($path));
130
	}
131
132
	public function stat($path) {
133
		clearstatcache();
134
		$fullPath = $this->getSourcePath($path);
135
		$statResult = stat($fullPath);
136
		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
137
			$filesize = $this->filesize($path);
138
			$statResult['size'] = $filesize;
139
			$statResult[7] = $filesize;
140
		}
141
		return $statResult;
142
	}
143
144
	public function filetype($path) {
145
		$filetype = filetype($this->getSourcePath($path));
146
		if ($filetype == 'link') {
147
			$filetype = filetype(realpath($this->getSourcePath($path)));
148
		}
149
		return $filetype;
150
	}
151
152
	public function filesize($path) {
153
		if ($this->is_dir($path)) {
154
			return 0;
155
		}
156
		$fullPath = $this->getSourcePath($path);
157
		if (PHP_INT_SIZE === 4) {
158
			$helper = new \OC\LargeFileHelper;
159
			return $helper->getFilesize($fullPath);
0 ignored issues
show
Bug Compatibility introduced by
The expression $helper->getFilesize($fullPath); of type integer|double adds the type double to the return on line 159 which is incompatible with the return type declared by the interface OCP\Files\Storage::filesize of type integer|false.
Loading history...
160
		}
161
		return filesize($fullPath);
162
	}
163
164
	public function isReadable($path) {
165
		return is_readable($this->getSourcePath($path));
166
	}
167
168
	public function isUpdatable($path) {
169
		return is_writable($this->getSourcePath($path));
170
	}
171
172
	public function file_exists($path) {
173
		return file_exists($this->getSourcePath($path));
174
	}
175
176
	public function filemtime($path) {
177
		$fullPath = $this->getSourcePath($path);
178
		clearstatcache($fullPath);
179
		if (!$this->file_exists($path)) {
180
			return false;
181
		}
182
		if (PHP_INT_SIZE === 4) {
183
			/**
184
			 * Check if exec is available to use before calling it.
185
			 */
186
			if (function_exists('exec') === true) {
187
				$result = 0;
188
				$returnVar = 0;
189
				if (\OC_Util::runningOn('linux')) {
190
					$result = (int)exec('stat -c %Y ' . escapeshellarg($fullPath), $output, $returnVar);
191
				} else if (\OC_Util::runningOn('bsd') || \OC_Util::runningOn('mac')) {
192
					$result = (int)exec('stat -f %m ' . escapeshellarg($fullPath), $output, $returnVar);
193
				}
194
195
				/**
196
				 * If the result is zero, then stat is missing.
197
				 * Additionally check the return status from the shell.
198
				 */
199
				if ($returnVar === 0) {
200
					return $result;
201
				}
202
			}
203
		}
204
		return filemtime($fullPath);
205
	}
206
207
	public function touch($path, $mtime = null) {
208
		// sets the modification time of the file to the given value.
209
		// If mtime is nil the current time is set.
210
		// note that the access time of the file always changes to the current time.
211
		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
212
			return false;
213
		}
214
		if (!is_null($mtime)) {
215
			$result = touch($this->getSourcePath($path), $mtime);
216
		} else {
217
			$result = touch($this->getSourcePath($path));
218
		}
219
		if ($result) {
220
			clearstatcache(true, $this->getSourcePath($path));
221
		}
222
223
		return $result;
224
	}
225
226
	public function file_get_contents($path) {
227
		return file_get_contents($this->getSourcePath($path));
228
	}
229
230
	public function file_put_contents($path, $data) {
231
		return file_put_contents($this->getSourcePath($path), $data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return file_put_contents...rcePath($path), $data); (integer) is incompatible with the return type declared by the interface OCP\Files\Storage::file_put_contents of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
232
	}
233
234 View Code Duplication
	public function unlink($path) {
235
		if ($this->is_dir($path)) {
236
			return $this->rmdir($path);
237
		} else if ($this->is_file($path)) {
238
			return unlink($this->getSourcePath($path));
239
		} else {
240
			return false;
241
		}
242
243
	}
244
245
	public function rename($path1, $path2) {
246
		$srcParent = dirname($path1);
247
		$dstParent = dirname($path2);
248
249
		if (!$this->isUpdatable($srcParent)) {
250
			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
251
			return false;
252
		}
253
254
		if (!$this->isUpdatable($dstParent)) {
255
			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
256
			return false;
257
		}
258
259
		if (!$this->file_exists($path1)) {
260
			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
261
			return false;
262
		}
263
264
		if ($this->is_dir($path2)) {
265
			$this->rmdir($path2);
266
		} else if ($this->is_file($path2)) {
267
			$this->unlink($path2);
268
		}
269
270
		if ($this->is_dir($path1)) {
271
			// we can't move folders across devices, use copy instead
272
			$stat1 = stat(dirname($this->getSourcePath($path1)));
273
			$stat2 = stat(dirname($this->getSourcePath($path2)));
274
			if ($stat1['dev'] !== $stat2['dev']) {
275
				$result = $this->copy($path1, $path2);
276
				if ($result) {
277
					$result &= $this->rmdir($path1);
278
				}
279
				return $result;
280
			}
281
		}
282
283
		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
284
	}
285
286
	public function copy($path1, $path2) {
287
		if ($this->is_dir($path1)) {
288
			return parent::copy($path1, $path2);
289
		} else {
290
			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
291
		}
292
	}
293
294
	public function fopen($path, $mode) {
295
		return fopen($this->getSourcePath($path), $mode);
296
	}
297
298
	public function hash($type, $path, $raw = false) {
299
		return hash_file($type, $this->getSourcePath($path), $raw);
300
	}
301
302
	public function free_space($path) {
303
		$sourcePath = $this->getSourcePath($path);
304
		// using !is_dir because $sourcePath might be a part file or
305
		// non-existing file, so we'd still want to use the parent dir
306
		// in such cases
307
		if (!is_dir($sourcePath)) {
308
			// disk_free_space doesn't work on files
309
			$sourcePath = dirname($sourcePath);
310
		}
311
		$space = @disk_free_space($sourcePath);
312
		if ($space === false || is_null($space)) {
313
			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
314
		}
315
		return $space;
316
	}
317
318
	public function search($query) {
319
		return $this->searchInDir($query);
320
	}
321
322
	public function getLocalFile($path) {
323
		return $this->getSourcePath($path);
324
	}
325
326
	public function getLocalFolder($path) {
327
		return $this->getSourcePath($path);
328
	}
329
330
	/**
331
	 * @param string $query
332
	 * @param string $dir
333
	 * @return array
334
	 */
335
	protected function searchInDir($query, $dir = '') {
336
		$files = [];
337
		$physicalDir = $this->getSourcePath($dir);
338
		foreach (scandir($physicalDir) as $item) {
339
			if (\OC\Files\Filesystem::isIgnoredDir($item))
340
				continue;
341
			$physicalItem = $physicalDir . '/' . $item;
342
343 View Code Duplication
			if (strstr(strtolower($item), strtolower($query)) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344
				$files[] = $dir . '/' . $item;
345
			}
346
			if (is_dir($physicalItem)) {
347
				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
348
			}
349
		}
350
		return $files;
351
	}
352
353
	/**
354
	 * check if a file or folder has been updated since $time
355
	 *
356
	 * @param string $path
357
	 * @param int $time
358
	 * @return bool
359
	 */
360
	public function hasUpdated($path, $time) {
361
		if ($this->file_exists($path)) {
362
			return $this->filemtime($path) > $time;
363
		} else {
364
			return true;
365
		}
366
	}
367
368
	/**
369
	 * Get the source path (on disk) of a given path
370
	 *
371
	 * @param string $path
372
	 * @return string
373
	 * @throws ForbiddenException
374
	 */
375
	public function getSourcePath($path) {
376
		$fullPath = $this->datadir . $path;
377
		if ($this->allowSymlinks || $path === '') {
378
			return $fullPath;
379
		}
380
		$pathToResolve = $fullPath;
381
		$realPath = realpath($pathToResolve);
382
		while ($realPath === false) { // for non existing files check the parent directory
383
			$pathToResolve = dirname($pathToResolve);
384
			$realPath = realpath($pathToResolve);
385
		}
386
		if ($realPath) {
387
			$realPath = $realPath . '/';
388
		}
389
390
		// Is broken symlink?
391
		if (is_link($fullPath) && !file_exists($fullPath)) {
392
			throw new ForbiddenException("$fullPath is a broken/dead symlink", false);
393
		}
394
395
		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
396
			return $fullPath;
397
		} else {
398
			throw new ForbiddenException("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", false);
399
		}
400
	}
401
402
	/**
403
	 * {@inheritdoc}
404
	 */
405
	public function isLocal() {
406
		return true;
407
	}
408
409
	/**
410
	 * get the ETag for a file or folder
411
	 *
412
	 * @param string $path
413
	 * @return string
414
	 */
415
	public function getETag($path) {
416
		if ($this->is_file($path)) {
417
			$stat = $this->stat($path);
418
			return md5(
419
				$stat['mtime'] .
420
				$stat['ino'] .
421
				$stat['dev'] .
422
				$stat['size']
423
			);
424
		} else {
425
			return parent::getETag($path);
426
		}
427
	}
428
429
	/**
430
	 * @param \OCP\Files\Storage $sourceStorage
431
	 * @param string $sourceInternalPath
432
	 * @param string $targetInternalPath
433
	 * @return bool
434
	 */
435 View Code Duplication
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
436
		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
437
			/**
438
			 * @var \OC\Files\Storage\Local $sourceStorage
439
			 */
440
			$rootStorage = new Local(['datadir' => '/']);
441
			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
442
		} else {
443
			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime);
444
		}
445
	}
446
447
	/**
448
	 * @param \OCP\Files\Storage $sourceStorage
449
	 * @param string $sourceInternalPath
450
	 * @param string $targetInternalPath
451
	 * @return bool
452
	 */
453 View Code Duplication
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
454
		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
455
			/**
456
			 * @var \OC\Files\Storage\Local $sourceStorage
457
			 */
458
			$rootStorage = new Local(['datadir' => '/']);
459
			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
460
		} else {
461
			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
462
		}
463
	}
464
}
465