Completed
Push — stable10 ( 12ec1d...43aa11 )
by Morris
34:28 queued 23:39
created

Local::is_file()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Brice Maron <[email protected]>
7
 * @author Jakob Sack <[email protected]>
8
 * @author Joas Schilling <[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
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OC\Files\Storage;
37
38
use OCP\Files\ForbiddenException;
39
40
/**
41
 * for local filestore, we only have to map the paths
42
 */
43
class Local extends \OC\Files\Storage\Common {
44
	protected $datadir;
45
46
	protected $dataDirLength;
47
48
	protected $allowSymlinks = false;
49
50
	protected $realDataDir;
51
52
	public function __construct($arguments) {
53
		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
54
			throw new \InvalidArgumentException('No data directory set for local storage');
55
		}
56
		$this->datadir = $arguments['datadir'];
57
		// some crazy code uses a local storage on root...
58
		if ($this->datadir === '/') {
59
			$this->realDataDir = $this->datadir;
60
		} else {
61
			$this->realDataDir = rtrim(realpath($this->datadir), '/') . '/';
62
		}
63
		if (substr($this->datadir, -1) !== '/') {
64
			$this->datadir .= '/';
65
		}
66
		$this->dataDirLength = strlen($this->realDataDir);
67
	}
68
69
	public function __destruct() {
70
	}
71
72
	public function getId() {
73
		return 'local::' . $this->datadir;
74
	}
75
76
	public function mkdir($path) {
77
		return @mkdir($this->getSourcePath($path), 0777, true);
78
	}
79
80
	public function rmdir($path) {
81
		if (!$this->isDeletable($path)) {
82
			return false;
83
		}
84
		try {
85
			$it = new \RecursiveIteratorIterator(
86
				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
87
				\RecursiveIteratorIterator::CHILD_FIRST
88
			);
89
			/**
90
			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
91
			 * This bug is fixed in PHP 5.5.9 or before
92
			 * See #8376
93
			 */
94
			$it->rewind();
95
			while ($it->valid()) {
96
				/**
97
				 * @var \SplFileInfo $file
98
				 */
99
				$file = $it->current();
100
				if (in_array($file->getBasename(), array('.', '..'))) {
101
					$it->next();
102
					continue;
103
				} elseif ($file->isDir()) {
104
					rmdir($file->getPathname());
105
				} elseif ($file->isFile() || $file->isLink()) {
106
					unlink($file->getPathname());
107
				}
108
				$it->next();
109
			}
110
			return rmdir($this->getSourcePath($path));
111
		} catch (\UnexpectedValueException $e) {
112
			return false;
113
		}
114
	}
115
116
	public function opendir($path) {
117
		return opendir($this->getSourcePath($path));
118
	}
119
120
	public function is_dir($path) {
121
		if (substr($path, -1) == '/') {
122
			$path = substr($path, 0, -1);
123
		}
124
		return is_dir($this->getSourcePath($path));
125
	}
126
127
	public function is_file($path) {
128
		return is_file($this->getSourcePath($path));
129
	}
130
131
	public function stat($path) {
132
		clearstatcache();
133
		$fullPath = $this->getSourcePath($path);
134
		$statResult = stat($fullPath);
135
		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
136
			$filesize = $this->filesize($path);
137
			$statResult['size'] = $filesize;
138
			$statResult[7] = $filesize;
139
		}
140
		return $statResult;
141
	}
142
143
	public function filetype($path) {
144
		$filetype = filetype($this->getSourcePath($path));
145
		if ($filetype == 'link') {
146
			$filetype = filetype(realpath($this->getSourcePath($path)));
147
		}
148
		return $filetype;
149
	}
150
151
	public function filesize($path) {
152
		if ($this->is_dir($path)) {
153
			return 0;
154
		}
155
		$fullPath = $this->getSourcePath($path);
156
		if (PHP_INT_SIZE === 4) {
157
			$helper = new \OC\LargeFileHelper;
158
			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 158 which is incompatible with the return type declared by the interface OCP\Files\Storage::filesize of type integer|false.
Loading history...
159
		}
160
		return filesize($fullPath);
161
	}
162
163
	public function isReadable($path) {
164
		return is_readable($this->getSourcePath($path));
165
	}
166
167
	public function isUpdatable($path) {
168
		return is_writable($this->getSourcePath($path));
169
	}
170
171
	public function file_exists($path) {
172
		return file_exists($this->getSourcePath($path));
173
	}
174
175
	public function filemtime($path) {
176
		clearstatcache($this->getSourcePath($path));
177
		return $this->file_exists($path) ? filemtime($this->getSourcePath($path)) : false;
178
	}
179
180
	public function touch($path, $mtime = null) {
181
		// sets the modification time of the file to the given value.
182
		// If mtime is nil the current time is set.
183
		// note that the access time of the file always changes to the current time.
184
		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
185
			return false;
186
		}
187
		if (!is_null($mtime)) {
188
			$result = touch($this->getSourcePath($path), $mtime);
189
		} else {
190
			$result = touch($this->getSourcePath($path));
191
		}
192
		if ($result) {
193
			clearstatcache(true, $this->getSourcePath($path));
194
		}
195
196
		return $result;
197
	}
198
199
	public function file_get_contents($path) {
200
		// file_get_contents() has a memory leak: https://bugs.php.net/bug.php?id=61961
201
		$fileName = $this->getSourcePath($path);
202
203
		$fileSize = filesize($fileName);
204
		if ($fileSize === 0) {
205
			return '';
206
		}
207
208
		$handle = fopen($fileName, 'rb');
209
		$content = fread($handle, $fileSize);
210
		fclose($handle);
211
		return $content;
212
	}
213
214
	public function file_put_contents($path, $data) {
215
		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...
216
	}
217
218 View Code Duplication
	public function unlink($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
219
		if ($this->is_dir($path)) {
220
			return $this->rmdir($path);
221
		} else if ($this->is_file($path)) {
222
			return unlink($this->getSourcePath($path));
223
		} else {
224
			return false;
225
		}
226
227
	}
228
229
	public function rename($path1, $path2) {
230
		$srcParent = dirname($path1);
231
		$dstParent = dirname($path2);
232
233
		if (!$this->isUpdatable($srcParent)) {
234
			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
235
			return false;
236
		}
237
238
		if (!$this->isUpdatable($dstParent)) {
239
			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
240
			return false;
241
		}
242
243
		if (!$this->file_exists($path1)) {
244
			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
245
			return false;
246
		}
247
248
		if ($this->is_dir($path2)) {
249
			$this->rmdir($path2);
250
		} else if ($this->is_file($path2)) {
251
			$this->unlink($path2);
252
		}
253
254
		if ($this->is_dir($path1)) {
255
			// we can't move folders across devices, use copy instead
256
			$stat1 = stat(dirname($this->getSourcePath($path1)));
257
			$stat2 = stat(dirname($this->getSourcePath($path2)));
258
			if ($stat1['dev'] !== $stat2['dev']) {
259
				$result = $this->copy($path1, $path2);
260
				if ($result) {
261
					$result &= $this->rmdir($path1);
262
				}
263
				return $result;
264
			}
265
		}
266
267
		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
268
	}
269
270
	public function copy($path1, $path2) {
271
		if ($this->is_dir($path1)) {
272
			return parent::copy($path1, $path2);
273
		} else {
274
			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
275
		}
276
	}
277
278
	public function fopen($path, $mode) {
279
		return fopen($this->getSourcePath($path), $mode);
280
	}
281
282
	public function hash($type, $path, $raw = false) {
283
		return hash_file($type, $this->getSourcePath($path), $raw);
284
	}
285
286
	public function free_space($path) {
287
		$sourcePath = $this->getSourcePath($path);
288
		// using !is_dir because $sourcePath might be a part file or
289
		// non-existing file, so we'd still want to use the parent dir
290
		// in such cases
291
		if (!is_dir($sourcePath)) {
292
			// disk_free_space doesn't work on files
293
			$sourcePath = dirname($sourcePath);
294
		}
295
		$space = @disk_free_space($sourcePath);
296
		if ($space === false || is_null($space)) {
297
			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
298
		}
299
		return $space;
300
	}
301
302
	public function search($query) {
303
		return $this->searchInDir($query);
304
	}
305
306
	public function getLocalFile($path) {
307
		return $this->getSourcePath($path);
308
	}
309
310
	public function getLocalFolder($path) {
311
		return $this->getSourcePath($path);
312
	}
313
314
	/**
315
	 * @param string $query
316
	 * @param string $dir
317
	 * @return array
318
	 */
319
	protected function searchInDir($query, $dir = '') {
320
		$files = array();
321
		$physicalDir = $this->getSourcePath($dir);
322
		foreach (scandir($physicalDir) as $item) {
323
			if (\OC\Files\Filesystem::isIgnoredDir($item))
324
				continue;
325
			$physicalItem = $physicalDir . '/' . $item;
326
327 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...
328
				$files[] = $dir . '/' . $item;
329
			}
330
			if (is_dir($physicalItem)) {
331
				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
332
			}
333
		}
334
		return $files;
335
	}
336
337
	/**
338
	 * check if a file or folder has been updated since $time
339
	 *
340
	 * @param string $path
341
	 * @param int $time
342
	 * @return bool
343
	 */
344
	public function hasUpdated($path, $time) {
345
		if ($this->file_exists($path)) {
346
			return $this->filemtime($path) > $time;
347
		} else {
348
			return true;
349
		}
350
	}
351
352
	/**
353
	 * Get the source path (on disk) of a given path
354
	 *
355
	 * @param string $path
356
	 * @return string
357
	 * @throws ForbiddenException
358
	 */
359
	public function getSourcePath($path) {
360
		$fullPath = $this->datadir . $path;
361
		if ($this->allowSymlinks || $path === '') {
362
			return $fullPath;
363
		}
364
		$pathToResolve = $fullPath;
365
		$realPath = realpath($pathToResolve);
366
		while ($realPath === false) { // for non existing files check the parent directory
367
			$pathToResolve = dirname($pathToResolve);
368
			$realPath = realpath($pathToResolve);
369
		}
370
		if ($realPath) {
371
			$realPath = $realPath . '/';
372
		}
373
		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
374
			return $fullPath;
375
		} else {
376
			throw new ForbiddenException("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", false);
377
		}
378
	}
379
380
	/**
381
	 * {@inheritdoc}
382
	 */
383
	public function isLocal() {
384
		return true;
385
	}
386
387
	/**
388
	 * get the ETag for a file or folder
389
	 *
390
	 * @param string $path
391
	 * @return string
392
	 */
393
	public function getETag($path) {
394
		if ($this->is_file($path)) {
395
			$stat = $this->stat($path);
396
			return md5(
397
				$stat['mtime'] .
398
				$stat['ino'] .
399
				$stat['dev'] .
400
				$stat['size']
401
			);
402
		} else {
403
			return parent::getETag($path);
404
		}
405
	}
406
407
	/**
408
	 * @param \OCP\Files\Storage $sourceStorage
409
	 * @param string $sourceInternalPath
410
	 * @param string $targetInternalPath
411
	 * @return bool
412
	 */
413 View Code Duplication
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
414
		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
415
			/**
416
			 * @var \OC\Files\Storage\Local $sourceStorage
417
			 */
418
			$rootStorage = new Local(['datadir' => '/']);
419
			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
420
		} else {
421
			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
422
		}
423
	}
424
425
	/**
426
	 * @param \OCP\Files\Storage $sourceStorage
427
	 * @param string $sourceInternalPath
428
	 * @param string $targetInternalPath
429
	 * @return bool
430
	 */
431 View Code Duplication
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
432
		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
433
			/**
434
			 * @var \OC\Files\Storage\Local $sourceStorage
435
			 */
436
			$rootStorage = new Local(['datadir' => '/']);
437
			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
438
		} else {
439
			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
440
		}
441
	}
442
}
443