Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
created

ObjectTree::copy()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 21
nc 7
nop 2
dl 0
loc 33
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Morris Jobke <[email protected]>
6
 * @author Robin Appelman <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 * @author Vincent Petry <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2016, ownCloud GmbH.
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OCA\DAV\Connector\Sabre;
28
29
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
30
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
31
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
32
use OC\Files\FileInfo;
33
use OC\Files\Mount\MoveableMount;
34
use OCP\Files\ForbiddenException;
35
use OCP\Files\StorageInvalidException;
36
use OCP\Files\StorageNotAvailableException;
37
use OCP\Lock\LockedException;
38
39
class ObjectTree extends \Sabre\DAV\Tree {
40
41
	/**
42
	 * @var \OC\Files\View
43
	 */
44
	protected $fileView;
45
46
	/**
47
	 * @var  \OCP\Files\Mount\IMountManager
48
	 */
49
	protected $mountManager;
50
51
	/**
52
	 * Creates the object
53
	 */
54
	public function __construct() {
55
	}
56
57
	/**
58
	 * @param \Sabre\DAV\INode $rootNode
59
	 * @param \OC\Files\View $view
60
	 * @param  \OCP\Files\Mount\IMountManager $mountManager
61
	 */
62
	public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) {
63
		$this->rootNode = $rootNode;
0 ignored issues
show
Documentation Bug introduced by
$rootNode is of type object<Sabre\DAV\INode>, but the property $rootNode was declared to be of type object<Sabre\DAV\ICollection>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
64
		$this->fileView = $view;
65
		$this->mountManager = $mountManager;
66
	}
67
68
	/**
69
	 * If the given path is a chunked file name, converts it
70
	 * to the real file name. Only applies if the OC-CHUNKED header
71
	 * is present.
72
	 *
73
	 * @param string $path chunk file path to convert
74
	 * 
75
	 * @return string path to real file
76
	 */
77
	private function resolveChunkFile($path) {
78
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
79
			// resolve to real file name to find the proper node
80
			list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path);
81
			if ($dir == '/' || $dir == '.') {
82
				$dir = '';
83
			}
84
85
			$info = \OC_FileChunking::decodeName($name);
86
			// only replace path if it was really the chunked file
87
			if (isset($info['transferid'])) {
88
				// getNodePath is called for multiple nodes within a chunk
89
				// upload call
90
				$path = $dir . '/' . $info['name'];
91
				$path = ltrim($path, '/');
92
			}
93
		}
94
		return $path;
95
	}
96
97
	public function cacheNode(Node $node) {
98
		$this->cache[trim($node->getPath(), '/')] = $node;
99
	}
100
101
	/**
102
	 * Returns the INode object for the requested path
103
	 *
104
	 * @param string $path
105
	 * @return \Sabre\DAV\INode
106
	 * @throws InvalidPath
107
	 * @throws \Sabre\DAV\Exception\Locked
108
	 * @throws \Sabre\DAV\Exception\NotFound
109
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
110
	 */
111
	public function getNodeForPath($path) {
112
		if (!$this->fileView) {
113
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
114
		}
115
116
		$path = trim($path, '/');
117
118
		if (isset($this->cache[$path])) {
119
			return $this->cache[$path];
120
		}
121
122
		if ($path) {
123
			try {
124
				$this->fileView->verifyPath($path, basename($path));
125
			} catch (\OCP\Files\InvalidPathException $ex) {
126
				throw new InvalidPath($ex->getMessage());
127
			}
128
		}
129
130
		// Is it the root node?
131
		if (!strlen($path)) {
132
			return $this->rootNode;
133
		}
134
135
		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
136
			// read from storage
137
			$absPath = $this->fileView->getAbsolutePath($path);
138
			$mount = $this->fileView->getMount($path);
139
			$storage = $mount->getStorage();
140
			$internalPath = $mount->getInternalPath($absPath);
141
			if ($storage && $storage->file_exists($internalPath)) {
142
				/**
143
				 * @var \OC\Files\Storage\Storage $storage
144
				 */
145
				// get data directly
146
				$data = $storage->getMetaData($internalPath);
147
				$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 138 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...
148
			} else {
149
				$info = null;
150
			}
151
		} else {
152
			// resolve chunk file name to real name, if applicable
153
			$path = $this->resolveChunkFile($path);
154
155
			// read from cache
156
			try {
157
				$info = $this->fileView->getFileInfo($path);
158
			} catch (StorageNotAvailableException $e) {
159
				throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available');
160
			} catch (StorageInvalidException $e) {
161
				throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid');
162
			} catch (LockedException $e) {
163
				throw new \Sabre\DAV\Exception\Locked();
164
			} catch (ForbiddenException $e) {
165
				throw new \Sabre\DAV\Exception\Forbidden();
166
			}
167
		}
168
169
		if (!$info) {
170
			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
171
		}
172
173
		if ($info->getType() === 'dir') {
174
			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this);
175
		} else {
176
			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
177
		}
178
179
		$this->cache[$path] = $node;
180
		return $node;
181
182
	}
183
184
	/**
185
	 * Moves a file from one location to another
186
	 *
187
	 * @param string $sourcePath The path to the file which should be moved
188
	 * @param string $destinationPath The full destination path, so not just the destination parent node
189
	 * @throws \Sabre\DAV\Exception\BadRequest
190
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
191
	 * @throws \Sabre\DAV\Exception\Forbidden
192
	 * @return int
193
	 */
194
	public function move($sourcePath, $destinationPath) {
195
		if (!$this->fileView) {
196
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
197
		}
198
199
		$targetNodeExists = $this->nodeExists($destinationPath);
200
		$sourceNode = $this->getNodeForPath($sourcePath);
201
		if ($sourceNode instanceof \Sabre\DAV\ICollection && $targetNodeExists) {
202
			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
203
		}
204
		list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourcePath);
205
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destinationPath);
206
207
		$isMovableMount = false;
208
		$sourceMount = $this->mountManager->find($this->fileView->getAbsolutePath($sourcePath));
209
		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
210
		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
211
			$isMovableMount = true;
212
		}
213
214
		try {
215
			$sameFolder = ($sourceDir === $destinationDir);
216
			// if we're overwriting or same folder
217
			if ($targetNodeExists || $sameFolder) {
218
				// note that renaming a share mount point is always allowed
219
				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
220
					throw new \Sabre\DAV\Exception\Forbidden();
221
				}
222
			} else {
223
				if (!$this->fileView->isCreatable($destinationDir)) {
224
					throw new \Sabre\DAV\Exception\Forbidden();
225
				}
226
			}
227
228
			if (!$sameFolder) {
229
				// moving to a different folder, source will be gone, like a deletion
230
				// note that moving a share mount point is always allowed
231
				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
232
					throw new \Sabre\DAV\Exception\Forbidden();
233
				}
234
			}
235
236
			$fileName = basename($destinationPath);
237
			try {
238
				$this->fileView->verifyPath($destinationDir, $fileName);
239
			} catch (\OCP\Files\InvalidPathException $ex) {
240
				throw new InvalidPath($ex->getMessage());
241
			}
242
243
			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
244
			if (!$renameOkay) {
245
				throw new \Sabre\DAV\Exception\Forbidden('');
246
			}
247
		} catch (StorageNotAvailableException $e) {
248
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
249
		} catch (ForbiddenException $ex) {
250
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
251
		} catch (LockedException $e) {
252
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
253
		}
254
255
		$this->markDirty($sourceDir);
256
		$this->markDirty($destinationDir);
257
258
	}
259
260
	/**
261
	 * Copies a file or directory.
262
	 *
263
	 * This method must work recursively and delete the destination
264
	 * if it exists
265
	 *
266
	 * @param string $source
267
	 * @param string $destination
268
	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
269
	 * @return void
270
	 */
271
	public function copy($source, $destination) {
272
		if (!$this->fileView) {
273
			throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
274
		}
275
276
		// this will trigger existence check
277
		$this->getNodeForPath($source);
278
279
		list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination);
280
		try {
281
			$this->fileView->verifyPath($destinationDir, $destinationName);
282
		} catch (\OCP\Files\InvalidPathException $ex) {
283
			throw new InvalidPath($ex->getMessage());
284
		}
285
286
		// Webdav's copy will implicitly do a delete+create, so only create+delete permissions are required
287
		if (!$this->fileView->isCreatable($destinationDir)) {
288
			throw new \Sabre\DAV\Exception\Forbidden();
289
		}
290
291
		try {
292
			$this->fileView->copy($source, $destination);
293
		} catch (StorageNotAvailableException $e) {
294
			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
295
		} catch (ForbiddenException $ex) {
296
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
297
		} catch (LockedException $e) {
298
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
299
		}
300
301
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
302
		$this->markDirty($destinationDir);
303
	}
304
}
305