Completed
Push — master ( ceb674...7cb467 )
by Morris
18:21 queued 11s
created

Directory::getQuotaInfo()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 8
nop 0
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Jakob Sack <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\DAV\Connector\Sabre;
33
34
use OC\Files\View;
35
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
36
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
37
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
38
use OCP\Files\FileInfo;
39
use OCP\Files\ForbiddenException;
40
use OCP\Files\InvalidPathException;
41
use OCP\Files\StorageNotAvailableException;
42
use OCP\Lock\ILockingProvider;
43
use OCP\Lock\LockedException;
44
use Sabre\DAV\Exception\Locked;
45
use Sabre\DAV\Exception\ServiceUnavailable;
46
use Sabre\DAV\INode;
47
use Sabre\DAV\Exception\BadRequest;
48
use OC\Files\Mount\MoveableMount;
49
use Sabre\DAV\IFile;
50
use Sabre\DAV\Exception\NotFound;
51
52
class Directory extends \OCA\DAV\Connector\Sabre\Node
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces between "Node" and comma; 1 found
Loading history...
53
	implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget {
0 ignored issues
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
54
55
	/**
56
	 * Cached directory content
57
	 *
58
	 * @var \OCP\Files\FileInfo[]
59
	 */
60
	private $dirContent;
61
62
	/**
63
	 * Cached quota info
64
	 *
65
	 * @var array
66
	 */
67
	private $quotaInfo;
68
69
	/**
70
	 * @var ObjectTree|null
71
	 */
72
	private $tree;
73
74
	/**
75
	 * Sets up the node, expects a full path name
76
	 *
77
	 * @param \OC\Files\View $view
78
	 * @param \OCP\Files\FileInfo $info
79
	 * @param ObjectTree|null $tree
80
	 * @param \OCP\Share\IManager $shareManager
81
	 */
82
	public function __construct(View $view, FileInfo $info, $tree = null, $shareManager = null) {
83
		parent::__construct($view, $info, $shareManager);
84
		$this->tree = $tree;
85
	}
86
87
	/**
88
	 * Creates a new file in the directory
89
	 *
90
	 * Data will either be supplied as a stream resource, or in certain cases
91
	 * as a string. Keep in mind that you may have to support either.
92
	 *
93
	 * After successful creation of the file, you may choose to return the ETag
94
	 * of the new file here.
95
	 *
96
	 * The returned ETag must be surrounded by double-quotes (The quotes should
97
	 * be part of the actual string).
98
	 *
99
	 * If you cannot accurately determine the ETag, you should not return it.
100
	 * If you don't store the file exactly as-is (you're transforming it
101
	 * somehow) you should also not return an ETag.
102
	 *
103
	 * This means that if a subsequent GET to this new file does not exactly
104
	 * return the same contents of what was submitted here, you are strongly
105
	 * recommended to omit the ETag.
106
	 *
107
	 * @param string $name Name of the file
108
	 * @param resource|string $data Initial payload
109
	 * @return null|string
110
	 * @throws Exception\EntityTooLarge
111
	 * @throws Exception\UnsupportedMediaType
112
	 * @throws FileLocked
113
	 * @throws InvalidPath
114
	 * @throws \Sabre\DAV\Exception
115
	 * @throws \Sabre\DAV\Exception\BadRequest
116
	 * @throws \Sabre\DAV\Exception\Forbidden
117
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
118
	 */
119
	public function createFile($name, $data = null) {
0 ignored issues
show
Coding Style introduced by
createFile uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
120
121
		try {
122
			// for chunked upload also updating a existing file is a "createFile"
123
			// because we create all the chunks before re-assemble them to the existing file.
124
			if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
125
126
				// exit if we can't create a new file and we don't updatable existing file
127
				$chunkInfo = \OC_FileChunking::decodeName($name);
128
				if (!$this->fileView->isCreatable($this->path) &&
129
					!$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name'])
130
				) {
131
					throw new \Sabre\DAV\Exception\Forbidden();
132
				}
133
134
			} else {
135
				// For non-chunked upload it is enough to check if we can create a new file
136
				if (!$this->fileView->isCreatable($this->path)) {
137
					throw new \Sabre\DAV\Exception\Forbidden();
138
				}
139
			}
140
141
			$this->fileView->verifyPath($this->path, $name);
142
143
			$path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
144
			// in case the file already exists/overwriting
145
			$info = $this->fileView->getFileInfo($this->path . '/' . $name);
146
			if (!$info) {
147
				// use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
148
				$info = new \OC\Files\FileInfo($path, null, null, [], null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<OC\Files\Storage\Storage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
null is of type null, but the function expects a object<OCP\Files\Mount\IMountPoint>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
149
			}
150
			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
151
152
			// only allow 1 process to upload a file at once but still allow reading the file while writing the part file
153
			$node->acquireLock(ILockingProvider::LOCK_SHARED);
154
			$this->fileView->lockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
155
156
			$result = $node->put($data);
157
158
			$this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
159
			$node->releaseLock(ILockingProvider::LOCK_SHARED);
160
			return $result;
161
		} catch (\OCP\Files\StorageNotAvailableException $e) {
162
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
163
		} catch (InvalidPathException $ex) {
164
			throw new InvalidPath($ex->getMessage(), false, $ex);
165
		} catch (ForbiddenException $ex) {
166
			throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
167
		} catch (LockedException $e) {
168
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
169
		}
170
	}
171
172
	/**
173
	 * Creates a new subdirectory
174
	 *
175
	 * @param string $name
176
	 * @throws FileLocked
177
	 * @throws InvalidPath
178
	 * @throws \Sabre\DAV\Exception\Forbidden
179
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
180
	 */
181
	public function createDirectory($name) {
182
		try {
183
			if (!$this->info->isCreatable()) {
184
				throw new \Sabre\DAV\Exception\Forbidden();
185
			}
186
187
			$this->fileView->verifyPath($this->path, $name);
188
			$newPath = $this->path . '/' . $name;
189
			if (!$this->fileView->mkdir($newPath)) {
190
				throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
191
			}
192
		} catch (\OCP\Files\StorageNotAvailableException $e) {
193
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
194
		} catch (InvalidPathException $ex) {
195
			throw new InvalidPath($ex->getMessage());
196
		} catch (ForbiddenException $ex) {
197
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
198
		} catch (LockedException $e) {
199
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
200
		}
201
	}
202
203
	/**
204
	 * Returns a specific child node, referenced by its name
205
	 *
206
	 * @param string $name
207
	 * @param \OCP\Files\FileInfo $info
208
	 * @return \Sabre\DAV\INode
209
	 * @throws InvalidPath
210
	 * @throws \Sabre\DAV\Exception\NotFound
211
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
212
	 */
213
	public function getChild($name, $info = null) {
214
		if (!$this->info->isReadable()) {
215
			// avoid detecting files through this way
216
			throw new NotFound();
217
		}
218
219
		$path = $this->path . '/' . $name;
220
		if (is_null($info)) {
221
			try {
222
				$this->fileView->verifyPath($this->path, $name);
223
				$info = $this->fileView->getFileInfo($path);
224
			} catch (\OCP\Files\StorageNotAvailableException $e) {
225
				throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
226
			} catch (InvalidPathException $ex) {
227
				throw new InvalidPath($ex->getMessage());
228
			} catch (ForbiddenException $e) {
229
				throw new \Sabre\DAV\Exception\Forbidden();
230
			}
231
		}
232
233
		if (!$info) {
234
			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
235
		}
236
237 View Code Duplication
		if ($info['mimetype'] === 'httpd/unix-directory') {
238
			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
239
		} else {
240
			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager);
241
		}
242
		if ($this->tree) {
243
			$this->tree->cacheNode($node);
244
		}
245
		return $node;
246
	}
247
248
	/**
249
	 * Returns an array with all the child nodes
250
	 *
251
	 * @return \Sabre\DAV\INode[]
252
	 * @throws \Sabre\DAV\Exception\Locked
253
	 * @throws \OCA\DAV\Connector\Sabre\Exception\Forbidden
254
	 */
255
	public function getChildren() {
256
		if (!is_null($this->dirContent)) {
257
			return $this->dirContent;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->dirContent; (OCP\Files\FileInfo[]) is incompatible with the return type documented by OCA\DAV\Connector\Sabre\Directory::getChildren of type Sabre\DAV\INode[].

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...
258
		}
259
		try {
260
			if (!$this->info->isReadable()) {
261
				// return 403 instead of 404 because a 404 would make
262
				// the caller believe that the collection itself does not exist
263
				throw new Forbidden('No read permissions');
264
			}
265
			$folderContent = $this->fileView->getDirectoryContent($this->path);
266
		} catch (LockedException $e) {
267
			throw new Locked();
268
		}
269
270
		$nodes = array();
271
		foreach ($folderContent as $info) {
272
			$node = $this->getChild($info->getName(), $info);
273
			$nodes[] = $node;
274
		}
275
		$this->dirContent = $nodes;
276
		return $this->dirContent;
277
	}
278
279
	/**
280
	 * Checks if a child exists.
281
	 *
282
	 * @param string $name
283
	 * @return bool
284
	 */
285
	public function childExists($name) {
286
		// note: here we do NOT resolve the chunk file name to the real file name
287
		// to make sure we return false when checking for file existence with a chunk
288
		// file name.
289
		// This is to make sure that "createFile" is still triggered
290
		// (required old code) instead of "updateFile".
291
		//
292
		// TODO: resolve chunk file name here and implement "updateFile"
293
		$path = $this->path . '/' . $name;
294
		return $this->fileView->file_exists($path);
295
296
	}
297
298
	/**
299
	 * Deletes all files in this directory, and then itself
300
	 *
301
	 * @return void
302
	 * @throws FileLocked
303
	 * @throws \Sabre\DAV\Exception\Forbidden
304
	 */
305
	public function delete() {
306
307
		if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
308
			throw new \Sabre\DAV\Exception\Forbidden();
309
		}
310
311
		try {
312
			if (!$this->fileView->rmdir($this->path)) {
313
				// assume it wasn't possible to remove due to permission issue
314
				throw new \Sabre\DAV\Exception\Forbidden();
315
			}
316
		} catch (ForbiddenException $ex) {
317
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
318
		} catch (LockedException $e) {
319
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
320
		}
321
	}
322
323
	/**
324
	 * Returns available diskspace information
325
	 *
326
	 * @return array
327
	 */
328
	public function getQuotaInfo() {
329
		if ($this->quotaInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->quotaInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
330
			return $this->quotaInfo;
331
		}
332
		try {
333
			$storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info);
334
			if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
335
				$free = \OCP\Files\FileInfo::SPACE_UNLIMITED;
336
			} else {
337
				$free = $storageInfo['free'];
338
			}
339
			$this->quotaInfo = array(
340
				$storageInfo['used'],
341
				$free
342
			);
343
			return $this->quotaInfo;
344
		} catch (\OCP\Files\StorageNotAvailableException $e) {
345
			return array(0, 0);
346
		}
347
	}
348
349
	/**
350
	 * Moves a node into this collection.
351
	 *
352
	 * It is up to the implementors to:
353
	 *   1. Create the new resource.
354
	 *   2. Remove the old resource.
355
	 *   3. Transfer any properties or other data.
356
	 *
357
	 * Generally you should make very sure that your collection can easily move
358
	 * the move.
359
	 *
360
	 * If you don't, just return false, which will trigger sabre/dav to handle
361
	 * the move itself. If you return true from this function, the assumption
362
	 * is that the move was successful.
363
	 *
364
	 * @param string $targetName New local file/collection name.
365
	 * @param string $fullSourcePath Full path to source node
366
	 * @param INode $sourceNode Source node itself
367
	 * @return bool
368
	 * @throws BadRequest
369
	 * @throws ServiceUnavailable
370
	 * @throws Forbidden
371
	 * @throws FileLocked
372
	 * @throws \Sabre\DAV\Exception\Forbidden
373
	 */
374
	public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
375
		if (!$sourceNode instanceof Node) {
376
			// it's a file of another kind, like FutureFile
377
			if ($sourceNode instanceof IFile) {
0 ignored issues
show
Bug introduced by
The class Sabre\DAV\IFile does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
378
				// fallback to default copy+delete handling
379
				return false;
380
			}
381
			throw new BadRequest('Incompatible node types');
382
		}
383
384
		if (!$this->fileView) {
385
			throw new ServiceUnavailable('filesystem not setup');
386
		}
387
388
		$destinationPath = $this->getPath() . '/' . $targetName;
389
390
391
		$targetNodeExists = $this->childExists($targetName);
392
393
		// at getNodeForPath we also check the path for isForbiddenFileOrDir
394
		// with that we have covered both source and destination
395
		if ($sourceNode instanceof Directory && $targetNodeExists) {
396
			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
0 ignored issues
show
Bug introduced by
Consider using $sourceNode->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
397
		}
398
399
		list($sourceDir,) = \Sabre\Uri\split($sourceNode->getPath());
400
		$destinationDir = $this->getPath();
401
402
		$sourcePath = $sourceNode->getPath();
403
404
		$isMovableMount = false;
405
		$sourceMount = \OC::$server->getMountManager()->find($this->fileView->getAbsolutePath($sourcePath));
406
		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
407
		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
408
			$isMovableMount = true;
409
		}
410
411
		try {
412
			$sameFolder = ($sourceDir === $destinationDir);
413
			// if we're overwriting or same folder
414
			if ($targetNodeExists || $sameFolder) {
415
				// note that renaming a share mount point is always allowed
416
				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
417
					throw new \Sabre\DAV\Exception\Forbidden();
418
				}
419
			} else {
420
				if (!$this->fileView->isCreatable($destinationDir)) {
421
					throw new \Sabre\DAV\Exception\Forbidden();
422
				}
423
			}
424
425
			if (!$sameFolder) {
426
				// moving to a different folder, source will be gone, like a deletion
427
				// note that moving a share mount point is always allowed
428
				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
429
					throw new \Sabre\DAV\Exception\Forbidden();
430
				}
431
			}
432
433
			$fileName = basename($destinationPath);
434
			try {
435
				$this->fileView->verifyPath($destinationDir, $fileName);
436
			} catch (InvalidPathException $ex) {
437
				throw new InvalidPath($ex->getMessage());
438
			}
439
440
			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
441
			if (!$renameOkay) {
442
				throw new \Sabre\DAV\Exception\Forbidden('');
443
			}
444
		} catch (StorageNotAvailableException $e) {
445
			throw new ServiceUnavailable($e->getMessage());
446
		} catch (ForbiddenException $ex) {
447
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
448
		} catch (LockedException $e) {
449
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
450
		}
451
452
		return true;
453
	}
454
}
455