Completed
Push — master ( d1f5ae...7fb3bb )
by Thomas
12:24
created

Local   F

Complexity

Total Complexity 90

Size/Duplication

Total Lines 424
Duplicated Lines 8.02 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 90
lcom 1
cbo 7
dl 34
loc 424
rs 2
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A copyFromStorage() 11 11 2
A moveFromStorage() 11 11 2
A __construct() 0 16 5
A __destruct() 0 2 1
A getId() 0 3 1
A mkdir() 0 3 1
B rmdir() 0 35 8
A opendir() 0 3 1
A is_dir() 0 6 2
A is_file() 0 3 1
A stat() 0 11 3
A filetype() 0 7 2
A filesize() 0 11 3
A isReadable() 0 3 1
A isUpdatable() 0 3 1
A file_exists() 0 3 1
B filemtime() 0 30 8
A touch() 0 18 5
A file_get_contents() 0 3 1
A file_put_contents() 0 3 1
A unlink() 9 9 3
B rename() 0 40 9
A copy() 0 7 2
A fopen() 0 3 1
A hash() 0 3 1
A free_space() 0 15 4
A getLocalFile() 0 3 1
A getLocalFolder() 0 3 1
A searchInDir() 3 18 5
A hasUpdated() 0 7 2
B getSourcePath() 0 26 8
A isLocal() 0 3 1
A getETag() 0 13 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Local often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Local, and based on these observations, apply Extract Interface, too.

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
use OCP\Files\Storage\IStorage;
41
42
/**
43
 * for local filestore, we only have to map the paths
44
 */
45
class Local extends Common {
46
	protected $datadir;
47
48
	protected $dataDirLength;
49
50
	protected $allowSymlinks = false;
51
52
	protected $realDataDir;
53
54
	public function __construct($arguments) {
55
		if (!isset($arguments['datadir']) || !\is_string($arguments['datadir'])) {
56
			throw new \InvalidArgumentException('No data directory set for local storage');
57
		}
58
		$this->datadir = $arguments['datadir'];
59
		// some crazy code uses a local storage on root...
60
		if ($this->datadir === '/') {
61
			$this->realDataDir = $this->datadir;
62
		} else {
63
			$this->realDataDir = \rtrim(\realpath($this->datadir), '/') . '/';
64
		}
65
		if (\substr($this->datadir, -1) !== '/') {
66
			$this->datadir .= '/';
67
		}
68
		$this->dataDirLength = \strlen($this->realDataDir);
69
	}
70
71
	public function __destruct() {
72
	}
73
74
	public function getId() {
75
		return 'local::' . $this->datadir;
76
	}
77
78
	public function mkdir($path) {
79
		return @\mkdir($this->getSourcePath($path), 0777, true);
80
	}
81
82
	public function rmdir($path) {
83
		if (!$this->isDeletable($path)) {
84
			return false;
85
		}
86
		try {
87
			$it = new \RecursiveIteratorIterator(
88
				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
89
				\RecursiveIteratorIterator::CHILD_FIRST
90
			);
91
			/**
92
			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
93
			 * This bug is fixed in PHP 5.5.9 or before
94
			 * See #8376
95
			 */
96
			$it->rewind();
97
			while ($it->valid()) {
98
				/**
99
				 * @var \SplFileInfo $file
100
				 */
101
				$file = $it->current();
102
				if (\in_array($file->getBasename(), ['.', '..'])) {
103
					$it->next();
104
					continue;
105
				} elseif ($file->isDir()) {
106
					\rmdir($file->getPathname());
107
				} elseif ($file->isFile() || $file->isLink()) {
108
					\unlink($file->getPathname());
109
				}
110
				$it->next();
111
			}
112
			return \rmdir($this->getSourcePath($path));
113
		} catch (\UnexpectedValueException $e) {
114
			return false;
115
		}
116
	}
117
118
	public function opendir($path) {
119
		return \opendir($this->getSourcePath($path));
120
	}
121
122
	public function is_dir($path) {
123
		if (\substr($path, -1) === '/') {
124
			$path = \substr($path, 0, -1);
125
		}
126
		return \is_dir($this->getSourcePath($path));
127
	}
128
129
	public function is_file($path) {
130
		return \is_file($this->getSourcePath($path));
131
	}
132
133
	public function stat($path) {
134
		\clearstatcache();
135
		$fullPath = $this->getSourcePath($path);
136
		$statResult = \stat($fullPath);
137
		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
138
			$filesize = $this->filesize($path);
139
			$statResult['size'] = $filesize;
140
			$statResult[7] = $filesize;
141
		}
142
		return $statResult;
143
	}
144
145
	public function filetype($path) {
146
		$filetype = \filetype($this->getSourcePath($path));
147
		if ($filetype === 'link') {
148
			$filetype = \filetype(\realpath($this->getSourcePath($path)));
149
		}
150
		return $filetype;
151
	}
152
153
	public function filesize($path) {
154
		if ($this->is_dir($path)) {
155
			return 0;
156
		}
157
		$fullPath = $this->getSourcePath($path);
158
		if (PHP_INT_SIZE === 4) {
159
			$helper = new \OC\LargeFileHelper;
160
			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 160 which is incompatible with the return type declared by the interface OCP\Files\Storage\IStorage::filesize of type integer|false.
Loading history...
161
		}
162
		return \filesize($fullPath);
163
	}
164
165
	public function isReadable($path) {
166
		return \is_readable($this->getSourcePath($path));
167
	}
168
169
	public function isUpdatable($path) {
170
		return \is_writable($this->getSourcePath($path));
171
	}
172
173
	public function file_exists($path) {
174
		return \file_exists($this->getSourcePath($path));
175
	}
176
177
	public function filemtime($path) {
178
		$fullPath = $this->getSourcePath($path);
179
		\clearstatcache($fullPath);
180
		if (!$this->file_exists($path)) {
181
			return false;
182
		}
183
		if (PHP_INT_SIZE === 4) {
184
			/**
185
			 * Check if exec is available to use before calling it.
186
			 */
187
			if (\function_exists('exec') === true) {
188
				$result = 0;
189
				$returnVar = 0;
190
				if (\OC_Util::runningOn('linux')) {
191
					$result = (int)\exec('stat -c %Y ' . \escapeshellarg($fullPath), $output, $returnVar);
192
				} elseif (\OC_Util::runningOn('bsd') || \OC_Util::runningOn('mac')) {
193
					$result = (int)\exec('stat -f %m ' . \escapeshellarg($fullPath), $output, $returnVar);
194
				}
195
196
				/**
197
				 * If the result is zero, then stat is missing.
198
				 * Additionally check the return status from the shell.
199
				 */
200
				if ($returnVar === 0) {
201
					return $result;
202
				}
203
			}
204
		}
205
		return \filemtime($fullPath);
206
	}
207
208
	public function touch($path, $mtime = null) {
209
		// sets the modification time of the file to the given value.
210
		// If mtime is nil the current time is set.
211
		// note that the access time of the file always changes to the current time.
212
		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
213
			return false;
214
		}
215
		if ($mtime !== null) {
216
			$result = \touch($this->getSourcePath($path), $mtime);
217
		} else {
218
			$result = \touch($this->getSourcePath($path));
219
		}
220
		if ($result) {
221
			\clearstatcache(true, $this->getSourcePath($path));
222
		}
223
224
		return $result;
225
	}
226
227
	public function file_get_contents($path) {
228
		return \file_get_contents($this->getSourcePath($path));
229
	}
230
231
	public function file_put_contents($path, $data) {
232
		return \file_put_contents($this->getSourcePath($path), $data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \file_put_content...rcePath($path), $data); (integer) is incompatible with the return type declared by the interface OCP\Files\Storage\IStorage::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...
233
	}
234
235 View Code Duplication
	public function unlink($path) {
236
		if ($this->is_dir($path)) {
237
			return $this->rmdir($path);
238
		} elseif ($this->is_file($path)) {
239
			return \unlink($this->getSourcePath($path));
240
		} else {
241
			return false;
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
		} elseif ($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 || $space === null) {
313
			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
314
		}
315
		return $space;
316
	}
317
318
	public function getLocalFile($path) {
319
		return $this->getSourcePath($path);
320
	}
321
322
	public function getLocalFolder($path) {
323
		return $this->getSourcePath($path);
324
	}
325
326
	/**
327
	 * @param string $query
328
	 * @param string $dir
329
	 * @return array
330
	 * @throws ForbiddenException
331
	 */
332
	protected function searchInDir($query, $dir = '') {
333
		$files = [];
334
		$physicalDir = $this->getSourcePath($dir);
335
		foreach (\scandir($physicalDir) as $item) {
336
			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
337
				continue;
338
			}
339
			$physicalItem = $physicalDir . '/' . $item;
340
341 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...
342
				$files[] = $dir . '/' . $item;
343
			}
344
			if (\is_dir($physicalItem)) {
345
				$files = \array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
346
			}
347
		}
348
		return $files;
349
	}
350
351
	/**
352
	 * check if a file or folder has been updated since $time
353
	 *
354
	 * @param string $path
355
	 * @param int $time
356
	 * @return bool
357
	 * @throws \OCP\Files\StorageNotAvailableException
358
	 */
359
	public function hasUpdated($path, $time) {
360
		if ($this->file_exists($path)) {
361
			return $this->filemtime($path) > $time;
362
		} else {
363
			return true;
364
		}
365
	}
366
367
	/**
368
	 * Get the source path (on disk) of a given path
369
	 *
370
	 * @param string $path
371
	 * @return string
372
	 * @throws ForbiddenException
373
	 */
374
	public function getSourcePath($path) {
375
		$fullPath = $this->datadir . $path;
376
		if ($this->allowSymlinks || $path === '') {
377
			return $fullPath;
378
		}
379
		$pathToResolve = $fullPath;
380
		$realPath = \realpath($pathToResolve);
381
		while ($realPath === false) { // for non existing files check the parent directory
382
			$pathToResolve = \dirname($pathToResolve);
383
			$realPath = \realpath($pathToResolve);
384
		}
385
		if ($realPath) {
386
			$realPath .= '/';
387
		}
388
389
		// Is broken symlink?
390
		if (\is_link($fullPath) && !\file_exists($fullPath)) {
391
			throw new ForbiddenException("$fullPath is a broken/dead symlink", false);
392
		}
393
394
		if (\substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
395
			return $fullPath;
396
		} else {
397
			throw new ForbiddenException("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", false);
398
		}
399
	}
400
401
	/**
402
	 * {@inheritdoc}
403
	 */
404
	public function isLocal() {
405
		return true;
406
	}
407
408
	/**
409
	 * get the ETag for a file or folder
410
	 *
411
	 * @param string $path
412
	 * @return string
413
	 * @throws \OCP\Files\StorageNotAvailableException
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
		}
425
426
		return parent::getETag($path);
427
	}
428
429
	/**
430
	 * @param IStorage $sourceStorage
431
	 * @param string $sourceInternalPath
432
	 * @param string $targetInternalPath
433
	 * @param bool $preserveMtime
434
	 * @return bool
435
	 * @throws ForbiddenException
436
	 * @throws \OCP\Files\StorageNotAvailableException
437
	 */
438 View Code Duplication
	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
439
		if ($sourceStorage->instanceOfStorage(__CLASS__)) {
440
			/**
441
			 * @var \OC\Files\Storage\Local $sourceStorage
442
			 */
443
			$rootStorage = new Local(['datadir' => '/']);
444
			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
445
		}
446
447
		return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime);
448
	}
449
450
	/**
451
	 * @param \OCP\Files\Storage\IStorage $sourceStorage
452
	 * @param string $sourceInternalPath
453
	 * @param string $targetInternalPath
454
	 * @return bool
455
	 * @throws \OCP\Files\StorageNotAvailableException
456
	 */
457 View Code Duplication
	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
458
		if ($sourceStorage->instanceOfStorage(__CLASS__)) {
459
			/**
460
			 * @var \OC\Files\Storage\Local $sourceStorage
461
			 */
462
			$rootStorage = new Local(['datadir' => '/']);
463
			return $rootStorage->rename($sourceStorage->getLocalFile($sourceInternalPath), $this->getLocalFile($targetInternalPath));
0 ignored issues
show
Security Bug introduced by
It seems like $sourceStorage->getLocalFile($sourceInternalPath) targeting OC\Files\Storage\Local::getLocalFile() can also be of type false; however, OC\Files\Storage\Local::rename() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
Security Bug introduced by
It seems like $this->getLocalFile($targetInternalPath) targeting OC\Files\Storage\Local::getLocalFile() can also be of type false; however, OC\Files\Storage\Local::rename() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
464
		}
465
466
		return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
467
	}
468
}
469