Completed
Push — master ( 5c9537...d1dd83 )
by Julius
08:37 queued 20s
created

WopiController   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 438
Duplicated Lines 5.25 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 5
dl 23
loc 438
ccs 0
cts 246
cp 0
rs 8.72
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 1
C checkFileInfo() 0 68 11
B getFile() 0 49 6
F putFile() 9 97 13
F putRelativeFile() 14 113 15

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 WopiController 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 WopiController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016-2017 Lukas Reschke <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
namespace OCA\Richdocuments\Controller;
23
24
use OC\Files\View;
25
use OCA\Richdocuments\Db\WopiMapper;
26
use OCA\Richdocuments\TemplateManager;
27
use OCA\Richdocuments\TokenManager;
28
use OCA\Richdocuments\Helper;
29
use OCP\AppFramework\Controller;
30
use OCP\AppFramework\Db\DoesNotExistException;
31
use OCP\AppFramework\Http;
32
use OCP\AppFramework\Http\JSONResponse;
33
use OCP\Files\File;
34
use OCP\Files\IRootFolder;
35
use OCP\IConfig;
36
use OCP\ILogger;
37
use OCP\IRequest;
38
use OCP\IURLGenerator;
39
use OCP\AppFramework\Http\StreamResponse;
40
use OCP\IUserManager;
41
use OCP\IUserSession;
42
43
class WopiController extends Controller {
44
	/** @var IRootFolder */
45
	private $rootFolder;
46
	/** @var IURLGenerator */
47
	private $urlGenerator;
48
	/** @var IConfig */
49
	private $config;
50
	/** @var TokenManager */
51
	private $tokenManager;
52
	/** @var IUserManager */
53
	private $userManager;
54
	/** @var WopiMapper */
55
	private $wopiMapper;
56
	/** @var ILogger */
57
	private $logger;
58
	/** @var IUserSession */
59
	private $userSession;
60
	/** @var TemplateManager */
61
	private $templateManager;
62
63
	// Signifies LOOL that document has been changed externally in this storage
64
	const LOOL_STATUS_DOC_CHANGED = 1010;
65
66
	/**
67
	 * @param string $appName
68
	 * @param IRequest $request
69
	 * @param IRootFolder $rootFolder
70
	 * @param IURLGenerator $urlGenerator
71
	 * @param IConfig $config
72
	 * @param TokenManager $tokenManager
73
	 * @param IUserManager $userManager
74
	 * @param WopiMapper $wopiMapper
75
	 * @param ILogger $logger
76
	 * @param IUserSession $userSession
77
	 * @param TemplateManager $templateManager
78
	 */
79
	public function __construct($appName,
80
								IRequest $request,
81
								IRootFolder $rootFolder,
82
								IURLGenerator $urlGenerator,
83
								IConfig $config,
84
								TokenManager $tokenManager,
85
								IUserManager $userManager,
86
								WopiMapper $wopiMapper,
87
								ILogger $logger,
88
								IUserSession $userSession,
89
								TemplateManager $templateManager) {
90
		parent::__construct($appName, $request);
91
		$this->rootFolder = $rootFolder;
92
		$this->urlGenerator = $urlGenerator;
93
		$this->config = $config;
94
		$this->tokenManager = $tokenManager;
95
		$this->userManager = $userManager;
96
		$this->wopiMapper = $wopiMapper;
97
		$this->logger = $logger;
98
		$this->userSession = $userSession;
99
		$this->templateManager = $templateManager;
100
	}
101
102
	/**
103
	 * Returns general info about a file.
104
	 *
105
	 * @NoAdminRequired
106
	 * @NoCSRFRequired
107
	 * @PublicPage
108
	 *
109
	 * @param string $fileId
110
	 * @param string $access_token
111
	 * @return JSONResponse
112
	 * @throws \OCP\Files\InvalidPathException
113
	 * @throws \OCP\Files\NotFoundException
114
	 */
115
	public function checkFileInfo($fileId, $access_token) {
116
		list($fileId, , $version) = Helper::parseFileId($fileId);
117
118
		try {
119
			$wopi = $this->wopiMapper->getPathForToken($access_token);
120
		} catch (DoesNotExistException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
121
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
122
		}
123
124
		if ($wopi->isTemplateToken()) {
125
			$this->templateManager->setUserId($wopi->getOwnerUid());
126
			$file = $this->templateManager->get($wopi->getFileid());
127
		} else {
128
			// Login the user to see his mount locations
129
			try {
130
				/** @var File $file */
131
				$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
132
				$file = $userFolder->getById($fileId)[0];
133
			} catch (\Exception $e) {
134
				$this->logger->logException($e, ['app' => 'richdocuments']);
135
				return new JSONResponse([], Http::STATUS_FORBIDDEN);
136
			}
137
		}
138
139
		if(!($file instanceof File)) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\File 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...
140
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
141
		}
142
143
		$isPublic = $wopi->getEditorUid() === null;
144
		$guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8);
145
		$response = [
146
			'BaseFileName' => $file->getName(),
147
			'Size' => $file->getSize(),
148
			'Version' => $version,
149
			'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId,
150
			'OwnerId' => $wopi->getOwnerUid(),
151
			'UserFriendlyName' => !$isPublic ? \OC_User::getDisplayName($wopi->getEditorUid()) : $wopi->getGuestDisplayname(),
152
			'UserExtraInfo' => [
153
			],
154
			'UserCanWrite' => $wopi->getCanwrite(),
155
			'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() || $isPublic,
156
			'PostMessageOrigin' => $wopi->getServerHost(),
157
			'LastModifiedTime' => Helper::toISO8601($file->getMTime()),
158
			'SupportsRename' => true,
159
			'UserCanRename' => !$isPublic,
160
			'EnableInsertRemoteImage' => true,
161
			'EnableShare' => true,
162
			'HideUserList' => 'desktop',
163
			'DisablePrint' => $wopi->getHideDownload(),
164
			'DisableExport' => $wopi->getHideDownload(),
165
			'DisableCopy' => $wopi->getHideDownload(),
166
			'HideExportOption' => $wopi->getHideDownload(),
167
			'HidePrintOption' => $wopi->getHideDownload()
168
		];
169
170
		if ($wopi->isTemplateToken()) {
171
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
172
			$file = $userFolder->getById($wopi->getTemplateDestination())[0];
173
			$response['TemplateSaveAs'] = $file->getName();
174
		}
175
176
		$user = $this->userManager->get($wopi->getEditorUid());
177
		if($user !== null && $user->getAvatarImage(32) !== null) {
178
			$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
179
		}
180
181
		return new JSONResponse($response);
182
	}
183
184
	/**
185
	 * Given an access token and a fileId, returns the contents of the file.
186
	 * Expects a valid token in access_token parameter.
187
	 *
188
	 * @PublicPage
189
	 * @NoCSRFRequired
190
	 *
191
	 * @param string $fileId
192
	 * @param string $access_token
193
	 * @return Http\Response
194
	 */
195
	public function getFile($fileId,
196
							$access_token) {
197
		list($fileId, , $version) = Helper::parseFileId($fileId);
198
199
		$wopi = $this->wopiMapper->getPathForToken($access_token);
200
201
		if ((int)$fileId !== $wopi->getFileid()) {
202
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
203
		}
204
205
		// Template is just returned as there is no version logic
206
		if ($wopi->isTemplateToken()) {
207
			$this->templateManager->setUserId($wopi->getOwnerUid());
208
			$file = $this->templateManager->get($wopi->getFileid());
209
			$response = new StreamResponse($file->fopen('rb'));
210
			$response->addHeader('Content-Disposition', 'attachment');
211
			$response->addHeader('Content-Type', 'application/octet-stream');
212
			return $response;
213
		}
214
215
		try {
216
			/** @var File $file */
217
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
218
			$file = $userFolder->getById($fileId)[0];
219
			\OC_User::setIncognitoMode(true);
220
			if ($version !== '0') {
221
				$view = new View('/' . $wopi->getOwnerUid() . '/files');
222
				$relPath = $view->getRelativePath($file->getPath());
223
				$versionPath = '/files_versions/' . $relPath . '.v' . $version;
224
				$view = new View('/' . $wopi->getOwnerUid());
225
				if ($view->file_exists($versionPath)){
226
					$response = new StreamResponse($view->fopen($versionPath, 'rb'));
227
				}
228
				else {
229
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
230
				}
231
			}
232
			else
233
			{
234
				$response = new StreamResponse($file->fopen('rb'));
235
			}
236
			$response->addHeader('Content-Disposition', 'attachment');
237
			$response->addHeader('Content-Type', 'application/octet-stream');
238
			return $response;
239
		} catch (\Exception $e) {
240
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'getFile failed']);
241
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
242
		}
243
	}
244
245
	/**
246
	 * Given an access token and a fileId, replaces the files with the request body.
247
	 * Expects a valid token in access_token parameter.
248
	 *
249
	 * @PublicPage
250
	 * @NoCSRFRequired
251
	 *
252
	 * @param string $fileId
253
	 * @param string $access_token
254
	 * @return JSONResponse
255
	 */
256
	public function putFile($fileId,
257
							$access_token) {
258
		list($fileId, ,) = Helper::parseFileId($fileId);
259
		$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
260
		$isRenameFile = ($this->request->getHeader('X-WOPI-Override') === 'RENAME_FILE');
0 ignored issues
show
Unused Code introduced by
$isRenameFile is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
261
262
		$wopi = $this->wopiMapper->getPathForToken($access_token);
263
		if (!$wopi->getCanwrite()) {
264
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
265
		}
266
267
		// Unless the editor is empty (public link) we modify the files as the current editor
268
		$editor = $wopi->getEditorUid();
269
		if ($editor === null) {
270
			$editor = $wopi->getOwnerUid();
271
		}
272
273
		try {
274
			/** @var File $file */
275
			$userFolder = $this->rootFolder->getUserFolder($editor);
276
			$file = $userFolder->getById($fileId)[0];
277
278
			if ($isPutRelative) {
279
				// the new file needs to be installed in the current user dir
280
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
281
				$file = $userFolder->getById($fileId)[0];
282
283
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
284
				$suggested = iconv('utf-7', 'utf-8', $suggested);
285
286 View Code Duplication
				if ($suggested[0] === '.') {
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...
287
					$path = dirname($file->getPath()) . '/New File' . $suggested;
288
				}
289
				else if ($suggested[0] !== '/') {
290
					$path = dirname($file->getPath()) . '/' . $suggested;
291
				}
292
				else {
293
					$path = $userFolder->getPath() . $suggested;
294
				}
295
296
				if ($path === '') {
297
					return new JSONResponse([
298
						'status' => 'error',
299
						'message' => 'Cannot create the file'
300
					]);
301
				}
302
303
				$root = \OC::$server->getRootFolder();
304
305
				// create the folder first
306
				if (!$root->nodeExists(dirname($path))) {
307
					$root->newFolder(dirname($path));
308
				}
309
310
				// create a unique new file
311
				$path = $root->getNonExistingName($path);
312
				$root->newFile($path);
313
				$file = $root->get($path);
314
			} else {
315
				$wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp');
316
				if (!is_null($wopiHeaderTime) && $wopiHeaderTime != Helper::toISO8601($file->getMTime())) {
317
					$this->logger->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [
318
						'headerTime' => $wopiHeaderTime,
319
						'storageTime' => Helper::toISO8601($file->getMTime())
320
					]);
321
					// Tell WOPI client about this conflict.
322
					return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT);
323
				}
324
			}
325
326
			$content = fopen('php://input', 'rb');
327
328
			// Set the user to register the change under his name
329
			$editor = $this->userManager->get($wopi->getEditorUid());
330
			if (!is_null($editor)) {
331
				$this->userSession->setUser($editor);
332
			}
333
334
			$file->putContent($content);
335
336
			if ($isPutRelative) {
337
				// generate a token for the new file (the user still has to be
338
				// logged in)
339
				list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
340
341
				$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
342
				$url = $this->urlGenerator->getAbsoluteURL($wopi);
343
344
				return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
345
			}
346
347
			return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMTime())]);
348
		} catch (\Exception $e) {
349
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'getFile failed']);
350
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
351
		}
352
	}
353
354
	/**
355
	 * Given an access token and a fileId, replaces the files with the request body.
356
	 * Expects a valid token in access_token parameter.
357
	 * Just actually routes to the PutFile, the implementation of PutFile
358
	 * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body.
359
	 *
360
	 * @PublicPage
361
	 * @NoCSRFRequired
362
	 *
363
	 * @param string $fileId
364
	 * @param string $access_token
365
	 * @return JSONResponse
366
	 */
367
	public function putRelativeFile($fileId,
368
					$access_token) {
369
		list($fileId, ,) = Helper::parseFileId($fileId);
370
		$wopi = $this->wopiMapper->getPathForToken($access_token);
371
		$isRenameFile = ($this->request->getHeader('X-WOPI-Override') === 'RENAME_FILE');
372
373
		if (!$wopi->getCanwrite()) {
374
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
375
		}
376
377
		// Unless the editor is empty (public link) we modify the files as the current editor
378
		$editor = $wopi->getEditorUid();
379
		if ($editor === null) {
380
			$editor = $wopi->getOwnerUid();
381
		}
382
383
		try {
384
			// the new file needs to be installed in the current user dir
385
			$userFolder = $this->rootFolder->getUserFolder($editor);
386
387
			if ($wopi->isTemplateToken()) {
388
				$this->templateManager->setUserId($wopi->getOwnerUid());
389
				$file = $userFolder->getById($wopi->getTemplateDestination())[0];
390
			} else if ($isRenameFile) {
391
				// the new file needs to be installed in the current user dir
392
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
393
				$file = $userFolder->getById($fileId)[0];
394
395
				$suggested = $this->request->getHeader('X-WOPI-RequestedName');
396
397
				$suggested = iconv('utf-7', 'utf-8', $suggested) . '.' . $file->getExtension();
398
399 View Code Duplication
				if ($suggested[0] === '.') {
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...
400
					$path = dirname($file->getPath()) . '/New File' . $suggested;
401
				}
402
				else if ($suggested[0] !== '/') {
403
					$path = dirname($file->getPath()) . '/' . $suggested;
404
				}
405
				else {
406
					$path = $userFolder->getPath() . $suggested;
407
				}
408
409
				if ($path === '') {
410
					return new JSONResponse([
411
						'status' => 'error',
412
						'message' => 'Cannot rename the file'
413
					]);
414
				}
415
416
				$root = \OC::$server->getRootFolder();
417
418
				// create the folder first
419
				if (!$root->nodeExists(dirname($path))) {
420
					$root->newFolder(dirname($path));
421
				}
422
423
				// create a unique new file
424
				$path = $root->getNonExistingName($path);
425
				$file = $file->move($path);
426
			} else {
427
				$file = $userFolder->getById($fileId)[0];
428
429
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
430
				$suggested = iconv('utf-7', 'utf-8', $suggested);
431
432 View Code Duplication
				if ($suggested[0] === '.') {
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...
433
					$path = dirname($file->getPath()) . '/New File' . $suggested;
434
				} else if ($suggested[0] !== '/') {
435
					$path = dirname($file->getPath()) . '/' . $suggested;
436
				} else {
437
					$path = $userFolder->getPath() . $suggested;
438
				}
439
440
				if ($path === '') {
441
					return new JSONResponse([
442
						'status' => 'error',
443
						'message' => 'Cannot create the file'
444
					]);
445
				}
446
447
				// create the folder first
448
				if (!$this->rootFolder->nodeExists(dirname($path))) {
449
					$this->rootFolder->newFolder(dirname($path));
450
				}
451
452
				// create a unique new file
453
				$path = $this->rootFolder->getNonExistingName($path);
454
				$file = $this->rootFolder->newFile($path);
455
			}
456
457
			$content = fopen('php://input', 'rb');
458
459
			// Set the user to register the change under his name
460
			$editor = $this->userManager->get($wopi->getEditorUid());
461
			if (!is_null($editor)) {
462
				$this->userSession->setUser($editor);
463
			}
464
465
			$file->putContent($content);
466
467
			// generate a token for the new file (the user still has to be
468
			// logged in)
469
			list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
470
471
			$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
472
			$url = $this->urlGenerator->getAbsoluteURL($wopi);
473
474
			return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
475
		} catch (\Exception $e) {
476
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'putRelativeFile failed']);
477
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
478
		}
479
	}
480
}
481