Completed
Push — master ( e4992c...6d0a35 )
by
unknown
10:42
created

FilesPlugin   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 384
Duplicated Lines 1.3 %

Coupling/Cohesion

Components 2
Dependencies 26

Importance

Changes 0
Metric Value
dl 5
loc 384
rs 8.8
c 0
b 0
f 0
wmc 45
lcom 2
cbo 26

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A initialize() 0 33 2
A checkMove() 0 19 5
A handleDownloadToken() 5 17 4
B httpGet() 0 45 10
C handleGetProperties() 0 93 12
A handleUpdateProperties() 0 23 5
B sendFileIdHeader() 0 22 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Lukas Reschke <[email protected]>
6
 * @author Michael Jobst <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2018, ownCloud GmbH
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\DAV\Connector\Sabre;
32
33
use OC\AppFramework\Http\Request;
34
use OCA\DAV\Files\IProvidesAdditionalHeaders;
35
use OCP\Files\ForbiddenException;
36
use OCP\Files\StorageNotAvailableException;
37
use OCP\IConfig;
38
use OCP\IRequest;
39
use Sabre\DAV\Exception\Forbidden;
40
use Sabre\DAV\Exception\NotFound;
41
use Sabre\DAV\IFile;
42
use Sabre\DAV\PropFind;
43
use Sabre\DAV\PropPatch;
44
use Sabre\DAV\ServerPlugin;
45
use Sabre\DAV\Tree;
46
use Sabre\HTTP\RequestInterface;
47
use Sabre\HTTP\ResponseInterface;
48
49
class FilesPlugin extends ServerPlugin {
50
51
	// namespace
52
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
53
	const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
54
	const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
55
	const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
56
	const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
57
	const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
58
	const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
59
	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
60
	const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
61
	const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
62
	const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
63
	const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
64
	const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
65
	const PRIVATE_LINK_PROPERTYNAME = '{http://owncloud.org/ns}privatelink';
66
67
	/**
68
	 * Reference to main server object
69
	 *
70
	 * @var \Sabre\DAV\Server
71
	 */
72
	private $server;
73
74
	/**
75
	 * @var Tree
76
	 */
77
	private $tree;
78
79
	/**
80
	 * Whether this is public webdav.
81
	 * If true, some returned information will be stripped off.
82
	 *
83
	 * @var bool
84
	 */
85
	private $isPublic;
86
87
	/**
88
	 * @var bool
89
	 */
90
	private $downloadAttachment;
91
92
	/**
93
	 * @var IConfig
94
	 */
95
	private $config;
96
97
	/**
98
	 * @var IRequest
99
	 */
100
	private $request;
101
102
	/**
103
	 * @param Tree $tree
104
	 * @param IConfig $config
105
	 * @param IRequest $request
106
	 * @param bool $isPublic
107
	 * @param bool $downloadAttachment
108
	 */
109
	public function __construct(Tree $tree,
110
								IConfig $config,
111
								IRequest $request,
112
								$isPublic = false,
113
								$downloadAttachment = true) {
114
		$this->tree = $tree;
115
		$this->config = $config;
116
		$this->request = $request;
117
		$this->isPublic = $isPublic;
118
		$this->downloadAttachment = $downloadAttachment;
119
	}
120
121
	/**
122
	 * This initializes the plugin.
123
	 *
124
	 * This function is called by \Sabre\DAV\Server, after
125
	 * addPlugin is called.
126
	 *
127
	 * This method should set up the required event subscriptions.
128
	 *
129
	 * @param \Sabre\DAV\Server $server
130
	 * @return void
131
	 */
132
	public function initialize(\Sabre\DAV\Server $server) {
133
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
134
		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
135
		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
136
		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
137
		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
138
		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
139
		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
140
		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
141
		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
142
		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
143
		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
144
		$server->protectedProperties[] = self::PRIVATE_LINK_PROPERTYNAME;
145
146
		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
147
		$allowedProperties = ['{DAV:}getetag'];
148
		$server->protectedProperties = \array_diff($server->protectedProperties, $allowedProperties);
149
150
		$this->server = $server;
151
		$this->server->on('propFind', [$this, 'handleGetProperties']);
152
		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
153
		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
154
		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
155
		$this->server->on('afterMethod:GET', [$this,'httpGet']);
156
		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
157
		$this->server->on('afterResponse', function ($request, ResponseInterface $response) {
158
			$body = $response->getBody();
159
			if (\is_resource($body)) {
160
				\fclose($body);
161
			}
162
		});
163
		$this->server->on('beforeMove', [$this, 'checkMove']);
164
	}
165
166
	/**
167
	 * Plugin that checks if a move can actually be performed.
168
	 *
169
	 * @param string $source source path
170
	 * @param string $destination destination path
171
	 * @throws Forbidden
172
	 * @throws NotFound
173
	 */
174
	public function checkMove($source, $destination) {
175
		$sourceNode = $this->tree->getNodeForPath($source);
176
		if (!$sourceNode instanceof Node) {
177
			return;
178
		}
179
		list($sourceDir, ) = \Sabre\HTTP\URLUtil::splitPath($source);
180
		list($destinationDir, ) = \Sabre\HTTP\URLUtil::splitPath($destination);
181
182
		if ($sourceDir !== $destinationDir) {
183
			$sourceNodeFileInfo = $sourceNode->getFileInfo();
184
			if ($sourceNodeFileInfo === null) {
185
				throw new NotFound($source . ' does not exist');
186
			}
187
188
			if (!$sourceNodeFileInfo->isDeletable()) {
189
				throw new Forbidden($source . " cannot be deleted");
190
			}
191
		}
192
	}
193
194
	/**
195
	 * This sets a cookie to be able to recognize the start of the download
196
	 * the content must not be longer than 32 characters and must only contain
197
	 * alphanumeric characters
198
	 *
199
	 * @param RequestInterface $request
200
	 * @param ResponseInterface $response
201
	 */
202
	public function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
203
		$queryParams = $request->getQueryParameters();
204
205
		/**
206
		 * this sets a cookie to be able to recognize the start of the download
207
		 * the content must not be longer than 32 characters and must only contain
208
		 * alphanumeric characters
209
		 */
210
		if (isset($queryParams['downloadStartSecret'])) {
211
			$token = $queryParams['downloadStartSecret'];
212 View Code Duplication
			if (!isset($token[32])
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...
213
				&& \preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
214
				// FIXME: use $response->setHeader() instead
215
				\setcookie('ocDownloadStarted', $token, \time() + 20, '/');
216
			}
217
		}
218
	}
219
220
	/**
221
	 * Add headers to file download
222
	 *
223
	 * @param RequestInterface $request
224
	 * @param ResponseInterface $response
225
	 */
226
	public function httpGet(RequestInterface $request, ResponseInterface $response) {
227
		// Only handle valid files
228
		$node = $this->tree->getNodeForPath($request->getPath());
229
		if (!($node instanceof IFile)) {
230
			return;
231
		}
232
233
		// adds a 'Content-Disposition: attachment' header
234
		if ($this->downloadAttachment) {
235
			$filename = $node->getName();
236
			if ($node instanceof IProvidesAdditionalHeaders) {
237
				$filename = $node->getContentDispositionFileName();
238
			}
239
			if ($this->request->isUserAgent(
240
				[
241
					Request::USER_AGENT_IE,
242
					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
243
					Request::USER_AGENT_FREEBOX,
244
				])) {
245
				$response->setHeader('Content-Disposition', 'attachment; filename="' . \rawurlencode($filename) . '"');
246
			} else {
247
				$response->setHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . \rawurlencode($filename)
248
													 . '; filename="' . \rawurlencode($filename) . '"');
249
			}
250
		}
251
252
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
253
			//Add OC-Checksum header
254
			/** @var $node File */
255
			$checksum = $node->getChecksum('sha1');
256
			if ($checksum !== null && $checksum !== '') {
257
				$response->addHeader('OC-Checksum', $checksum);
258
			}
259
			// disable nginx buffering so big downloads through ownCloud won't
260
			// cause memory problems in the nginx process.
261
			$response->addHeader('X-Accel-Buffering', 'no');
262
		}
263
264
		if ($node instanceof IProvidesAdditionalHeaders) {
265
			$headers = $node->getHeaders();
266
			if (\is_array($headers)) {
267
				$response->addHeaders($headers);
268
			}
269
		}
270
	}
271
272
	/**
273
	 * Adds all ownCloud-specific properties
274
	 *
275
	 * @param PropFind $propFind
276
	 * @param \Sabre\DAV\INode $node
277
	 * @return void
278
	 */
279
	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
280
		$httpRequest = $this->server->httpRequest;
281
282
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
283
			if (!$node->getFileInfo()->isReadable()) {
284
				// avoid detecting files through this means
285
				throw new NotFound();
286
			}
287
288
			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
289
				return $node->getFileId();
290
			});
291
292
			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
293
				return $node->getInternalFileId();
294
			});
295
296
			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
297
				$perms = $node->getDavPermissions();
298
				if ($this->isPublic) {
299
					// remove mount information
300
					$perms = \str_replace(['S', 'M'], '', $perms);
301
				}
302
				return $perms;
303
			});
304
305
			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
306
				return $node->getSharePermissions(
307
					$httpRequest->getRawServerValue('PHP_AUTH_USER')
308
				);
309
			});
310
311
			$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node) {
312
				return $node->getETag();
313
			});
314
315
			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node) {
316
				$owner = $node->getOwner();
317
				return $owner->getUID();
318
			});
319
			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node) {
320
				$owner = $node->getOwner();
321
				$displayName = $owner->getDisplayName();
322
				return $displayName;
323
			});
324
			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
325
				return $node->getSize();
326
			});
327
328
			$propFind->handle(self::PRIVATE_LINK_PROPERTYNAME, function () use ($node) {
329
				return \OC::$server->getURLGenerator()
330
					->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileId' => $node->getInternalFileId()]);
331
			});
332
		}
333
334
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
335
			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () use ($node) {
336
				return $this->config->getSystemValue('data-fingerprint', '');
337
			});
338
		}
339
340
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
341
			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
342
				/** @var $node \OCA\DAV\Connector\Sabre\File */
343
				try {
344
					$directDownloadUrl = $node->getDirectDownload();
345
					if (isset($directDownloadUrl['url'])) {
346
						return $directDownloadUrl['url'];
347
					}
348
				} catch (StorageNotAvailableException $e) {
349
					return false;
350
				} catch (ForbiddenException $e) {
351
					return false;
352
				}
353
				return false;
354
			});
355
356
			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
357
				$checksum = $node->getChecksum();
358
				if ($checksum === null || $checksum === '') {
359
					return null;
360
				}
361
362
				return new ChecksumList($checksum);
363
			});
364
		}
365
366
		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
367
			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
368
				return $node->getSize();
369
			});
370
		}
371
	}
372
373
	/**
374
	 * Update ownCloud-specific properties
375
	 *
376
	 * @param string $path
377
	 * @param PropPatch $propPatch
378
	 *
379
	 * @return void
380
	 */
381
	public function handleUpdateProperties($path, PropPatch $propPatch) {
382
		$node = $this->tree->getNodeForPath($path);
383
		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
384
			return;
385
		}
386
387
		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
388
			if (empty($time)) {
389
				return false;
390
			}
391
			$node->touch($time);
392
			return true;
393
		});
394
		$propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
395
			if (empty($etag)) {
396
				return false;
397
			}
398
			if ($node->setEtag($etag) !== -1) {
399
				return true;
400
			}
401
			return false;
402
		});
403
	}
404
405
	/**
406
	 * @param string $filePath
407
	 * @param \Sabre\DAV\INode $node
408
	 * @throws \Sabre\DAV\Exception\BadRequest
409
	 */
410
	public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
411
		// chunked upload handling
412
		if (\OC_FileChunking::isWebdavChunk()) {
413
			list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath);
414
			$info = \OC_FileChunking::decodeName($name);
415
			if (!empty($info)) {
416
				$filePath = $path . '/' . $info['name'];
417
			}
418
		}
419
420
		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
421
		if (!$this->server->tree->nodeExists($filePath)) {
422
			return;
423
		}
424
		$node = $this->server->tree->getNodeForPath($filePath);
425
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
426
			$fileId = $node->getFileId();
427
			if ($fileId !== null) {
428
				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
429
			}
430
		}
431
	}
432
}
433