Completed
Pull Request — stable10 (#1320)
by Joas
25:20 queued 16:08
created

ObjectTree   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 301
rs 6.0975
wmc 60
lcom 1
cbo 12

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A init() 0 5 1
B resolveChunkFile() 0 19 5
A cacheNode() 0 3 1
C getNodeForPath() 0 72 15
F move() 0 83 27
D copy() 0 39 10

How to fix   Complexity   

Complex Class

Complex classes like ObjectTree 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 ObjectTree, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\DAV\Connector\Sabre;
31
32
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
33
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
34
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
35
use OC\Files\FileInfo;
36
use OC\Files\Mount\MoveableMount;
37
use OCP\Files\ForbiddenException;
38
use OCP\Files\StorageInvalidException;
39
use OCP\Files\StorageNotAvailableException;
40
use OCP\Lock\LockedException;
41
42
class ObjectTree extends \Sabre\DAV\Tree {
43
44
	/**
45
	 * @var \OC\Files\View
46
	 */
47
	protected $fileView;
48
49
	/**
50
	 * @var  \OCP\Files\Mount\IMountManager
51
	 */
52
	protected $mountManager;
53
54
	/**
55
	 * Creates the object
56
	 */
57
	public function __construct() {
58
	}
59
60
	/**
61
	 * @param \Sabre\DAV\INode $rootNode
62
	 * @param \OC\Files\View $view
63
	 * @param  \OCP\Files\Mount\IMountManager $mountManager
64
	 */
65
	public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) {
66
		$this->rootNode = $rootNode;
67
		$this->fileView = $view;
68
		$this->mountManager = $mountManager;
69
	}
70
71
	/**
72
	 * If the given path is a chunked file name, converts it
73
	 * to the real file name. Only applies if the OC-CHUNKED header
74
	 * is present.
75
	 *
76
	 * @param string $path chunk file path to convert
77
	 *
78
	 * @return string path to real file
79
	 */
80
	private function resolveChunkFile($path) {
81
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
82
			// resolve to real file name to find the proper node
83
			list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path);
84
			if ($dir == '/' || $dir == '.') {
85
				$dir = '';
86
			}
87
88
			$info = \OC_FileChunking::decodeName($name);
89
			// only replace path if it was really the chunked file
90
			if (isset($info['transferid'])) {
91
				// getNodePath is called for multiple nodes within a chunk
92
				// upload call
93
				$path = $dir . '/' . $info['name'];
94
				$path = ltrim($path, '/');
95
			}
96
		}
97
		return $path;
98
	}
99
100
	public function cacheNode(Node $node) {
101
		$this->cache[trim($node->getPath(), '/')] = $node;
102
	}
103
104
	/**
105
	 * Returns the INode object for the requested path
106
	 *
107
	 * @param string $path
108
	 * @return \Sabre\DAV\INode
109
	 * @throws InvalidPath
110
	 * @throws \Sabre\DAV\Exception\Locked
111
	 * @throws \Sabre\DAV\Exception\NotFound
112
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
113
	 */
114
	public function getNodeForPath($path) {
115
		if (!$this->fileView) {
116
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
117
		}
118
119
		$path = trim($path, '/');
120
121
		if (isset($this->cache[$path])) {
122
			return $this->cache[$path];
123
		}
124
125
		if ($path) {
126
			try {
127
				$this->fileView->verifyPath($path, basename($path));
128
			} catch (\OCP\Files\InvalidPathException $ex) {
129
				throw new InvalidPath($ex->getMessage());
130
			}
131
		}
132
133
		// Is it the root node?
134
		if (!strlen($path)) {
135
			return $this->rootNode;
136
		}
137
138
		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
139
			// read from storage
140
			$absPath = $this->fileView->getAbsolutePath($path);
141
			$mount = $this->fileView->getMount($path);
142
			$storage = $mount->getStorage();
143
			$internalPath = $mount->getInternalPath($absPath);
144
			if ($storage && $storage->file_exists($internalPath)) {
145
				/**
146
				 * @var \OC\Files\Storage\Storage $storage
147
				 */
148
				// get data directly
149
				$data = $storage->getMetaData($internalPath);
150
				$info = new FileInfo($absPath, $storage, $internalPath, $data, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->fileView->getMount($path) on line 141 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
151
			} else {
152
				$info = null;
153
			}
154
		} else {
155
			// resolve chunk file name to real name, if applicable
156
			$path = $this->resolveChunkFile($path);
157
158
			// read from cache
159
			try {
160
				$info = $this->fileView->getFileInfo($path);
161
			} catch (StorageNotAvailableException $e) {
162
				throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available');
163
			} catch (StorageInvalidException $e) {
164
				throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid');
165
			} catch (LockedException $e) {
166
				throw new \Sabre\DAV\Exception\Locked();
167
			} catch (ForbiddenException $e) {
168
				throw new \Sabre\DAV\Exception\Forbidden();
169
			}
170
		}
171
172
		if (!$info) {
173
			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
174
		}
175
176
		if ($info->getType() === 'dir') {
177
			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this);
178
		} else {
179
			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
180
		}
181
182
		$this->cache[$path] = $node;
183
		return $node;
184
185
	}
186
187
	/**
188
	 * Moves a file from one location to another
189
	 *
190
	 * @param string $sourcePath The path to the file which should be moved
191
	 * @param string $destinationPath The full destination path, so not just the destination parent node
192
	 * @throws FileLocked
193
	 * @throws Forbidden
194
	 * @throws InvalidPath
195
	 * @throws \Sabre\DAV\Exception\Forbidden
196
	 * @throws \Sabre\DAV\Exception\Locked
197
	 * @throws \Sabre\DAV\Exception\NotFound
198
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
199
	 * @return int
200
	 */
201
	public function move($sourcePath, $destinationPath) {
202
		if (!$this->fileView) {
203
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
204
		}
205
206
		$infoDestination = $this->fileView->getFileInfo(dirname($destinationPath));
207
		if (dirname($destinationPath) === dirname($sourcePath)) {
208
			$sourcePermission = $infoDestination && $infoDestination->isUpdateable();
209
			$destinationPermission = $sourcePermission;
210
		} else {
211
			$infoSource = $this->fileView->getFileInfo($sourcePath);
212
			if ($this->fileView->file_exists($destinationPath)) {
213
				$destinationPermission = $infoDestination && $infoDestination->isUpdateable();
214
			} else {
215
				$destinationPermission = $infoDestination && $infoDestination->isCreatable();
216
			}
217
			$sourcePermission =  $infoSource && $infoSource->isDeletable();
218
		}
219
220
		if (!$destinationPermission || !$sourcePermission) {
221
			throw new Forbidden('No permissions to move object.');
222
		}
223
224
		$targetNodeExists = $this->nodeExists($destinationPath);
225
		$sourceNode = $this->getNodeForPath($sourcePath);
226
		if ($sourceNode instanceof \Sabre\DAV\ICollection && $targetNodeExists) {
0 ignored issues
show
Bug introduced by
The class Sabre\DAV\ICollection 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...
227
			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
228
		}
229
		list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourcePath);
230
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destinationPath);
231
232
		$isMovableMount = false;
233
		$sourceMount = $this->mountManager->find($this->fileView->getAbsolutePath($sourcePath));
234
		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
235
		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
236
			$isMovableMount = true;
237
		}
238
239
		try {
240
			$sameFolder = ($sourceDir === $destinationDir);
241
			// if we're overwriting or same folder
242
			if ($targetNodeExists || $sameFolder) {
243
				// note that renaming a share mount point is always allowed
244
				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
245
					throw new \Sabre\DAV\Exception\Forbidden();
246
				}
247
			} else {
248
				if (!$this->fileView->isCreatable($destinationDir)) {
249
					throw new \Sabre\DAV\Exception\Forbidden();
250
				}
251
			}
252
253
			if (!$sameFolder) {
254
				// moving to a different folder, source will be gone, like a deletion
255
				// note that moving a share mount point is always allowed
256
				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
257
					throw new \Sabre\DAV\Exception\Forbidden();
258
				}
259
			}
260
261
			$fileName = basename($destinationPath);
262
			try {
263
				$this->fileView->verifyPath($destinationDir, $fileName);
264
			} catch (\OCP\Files\InvalidPathException $ex) {
265
				throw new InvalidPath($ex->getMessage());
266
			}
267
268
			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
269
			if (!$renameOkay) {
270
				throw new \Sabre\DAV\Exception\Forbidden('');
271
			}
272
		} catch (StorageNotAvailableException $e) {
273
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
274
		} catch (ForbiddenException $ex) {
275
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
276
		} catch (LockedException $e) {
277
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
278
		}
279
280
		$this->markDirty($sourceDir);
281
		$this->markDirty($destinationDir);
282
283
	}
284
285
	/**
286
	 * Copies a file or directory.
287
	 *
288
	 * This method must work recursively and delete the destination
289
	 * if it exists
290
	 *
291
	 * @param string $source
292
	 * @param string $destination
293
	 * @throws FileLocked
294
	 * @throws Forbidden
295
	 * @throws InvalidPath
296
	 * @throws \Exception
297
	 * @throws \Sabre\DAV\Exception\Forbidden
298
	 * @throws \Sabre\DAV\Exception\Locked
299
	 * @throws \Sabre\DAV\Exception\NotFound
300
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
301
	 * @return void
302
	 */
303
	public function copy($source, $destination) {
304
		if (!$this->fileView) {
305
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
306
		}
307
308
309
		$info = $this->fileView->getFileInfo(dirname($destination));
310
		if ($this->fileView->file_exists($destination)) {
311
			$destinationPermission = $info && $info->isUpdateable();
312
		} else {
313
			$destinationPermission = $info && $info->isCreatable();
314
		}
315
		if (!$destinationPermission) {
316
			throw new Forbidden('No permissions to copy object.');
317
		}
318
319
		// this will trigger existence check
320
		$this->getNodeForPath($source);
321
322
		list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination);
323
		try {
324
			$this->fileView->verifyPath($destinationDir, $destinationName);
325
		} catch (\OCP\Files\InvalidPathException $ex) {
326
			throw new InvalidPath($ex->getMessage());
327
		}
328
329
		try {
330
			$this->fileView->copy($source, $destination);
331
		} catch (StorageNotAvailableException $e) {
332
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
333
		} catch (ForbiddenException $ex) {
334
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
335
		} catch (LockedException $e) {
336
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
337
		}
338
339
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
340
		$this->markDirty($destinationDir);
341
	}
342
}
343