Completed
Push — master ( 9f31a8...0751c2 )
by Thomas
13:51
created

Local::__construct()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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