Completed
Push — stable10 ( ffaa25...9b0fca )
by Morris
32:12 queued 22:58
created

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