Completed
Pull Request — master (#31)
by Blizzz
13:09 queued 04:36
created

Local::mkdir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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