Completed
Push — master ( cac53c...4b57b6 )
by
unknown
02:17
created

WopiController::putFile()   F

Complexity

Conditions 13
Paths 369

Size

Total Lines 100
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 0
loc 100
ccs 0
cts 76
cp 0
rs 3.7737
c 0
b 0
f 0
cc 13
eloc 59
nc 369
nop 2
crap 182

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\TokenManager;
27
use OCA\Richdocuments\Db\Wopi;
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\IRequest;
37
use OCP\IURLGenerator;
38
use OCP\AppFramework\Http\StreamResponse;
39
use OCP\IUserManager;
40
41
class WopiController extends Controller {
42
	/** @var IRootFolder */
43
	private $rootFolder;
44
	/** @var IURLGenerator */
45
	private $urlGenerator;
46
	/** @var IConfig */
47
	private $config;
48
	/** @var TokenManager */
49
	private $tokenManager;
50
	/** @var IUserManager */
51
	private $userManager;
52
	/** @var WopiMapper */
53
	private $wopiMapper;
54
55
	// Signifies LOOL that document has been changed externally in this storage
56
	const LOOL_STATUS_DOC_CHANGED = 1010;
57
58
	/**
59
	 * @param string $appName
60
	 * @param string $UserId
61
	 * @param IRequest $request
62
	 * @param IRootFolder $rootFolder
63
	 * @param IURLGenerator $urlGenerator
64
	 * @param IConfig $config
65
	 * @param TokenManager $tokenManager
66
	 * @param IUserManager $userManager
67
	 * @param WopiMapper $wopiMapper
68
	 */
69 View Code Duplication
	public function __construct($appName,
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
70
								$UserId,
71
								IRequest $request,
72
								IRootFolder $rootFolder,
73
								IURLGenerator $urlGenerator,
74
								IConfig $config,
75
								TokenManager $tokenManager,
76
								IUserManager $userManager,
77
								WopiMapper $wopiMapper) {
78
		parent::__construct($appName, $request);
79
		$this->rootFolder = $rootFolder;
80
		$this->urlGenerator = $urlGenerator;
81
		$this->config = $config;
82
		$this->tokenManager = $tokenManager;
83
		$this->userManager = $userManager;
84
		$this->wopiMapper = $wopiMapper;
85
	}
86
87
	/**
88
	 * Returns general info about a file.
89
	 *
90
	 * @NoAdminRequired
91
	 * @NoCSRFRequired
92
	 * @PublicPage
93
	 *
94
	 * @param string $fileId
95
	 * @return JSONResponse
96
	 */
97
	public function checkFileInfo($fileId) {
98
		$token = $this->request->getParam('access_token');
99
100
		list($fileId, , $version) = Helper::parseFileId($fileId);
101
102
		try {
103
			$wopi = $this->wopiMapper->getPathForToken($token);
104
		} 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...
105
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
106
		}
107
108
		// Login the user to see his mount locations
109
		try {
110
			/** @var File $file */
111
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
112
			$file = $userFolder->getById($fileId)[0];
113
		} catch (\Exception $e) {
114
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
115
		}
116
117
		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...
118
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
119
		}
120
121
		$response = [
122
			'BaseFileName' => $file->getName(),
123
			'Size' => $file->getSize(),
124
			'Version' => $version,
125
			'UserId' => !is_null($wopi->getEditorUid()) ? $wopi->getEditorUid() : 'guest',
126
			'OwnerId' => $wopi->getOwnerUid(),
127
			'UserFriendlyName' => !is_null($wopi->getEditorUid()) ? \OC_User::getDisplayName($wopi->getEditorUid()) : 'Guest user',
128
			'UserExtraInfo' => [
129
			],
130
			'UserCanWrite' => $wopi->getCanwrite(),
131
			'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() ? true : is_null($wopi->getEditorUid()),
132
			'PostMessageOrigin' => $wopi->getServerHost(),
133
			'LastModifiedTime' => Helper::toISO8601($file->getMtime())
134
		];
135
136
		$serverVersion = $this->config->getSystemValue('version');
137
		if (version_compare($serverVersion, '13', '>=')) {
138
			$user = $this->userManager->get($wopi->getEditorUid());
139
			if($user !== null) {
140
				if($user->getAvatarImage(32) !== null) {
141
					$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
142
				}
143
			}
144
		}
145
146
		return new JSONResponse($response);
147
	}
148
149
	/**
150
	 * Given an access token and a fileId, returns the contents of the file.
151
	 * Expects a valid token in access_token parameter.
152
	 *
153
	 * @PublicPage
154
	 * @NoCSRFRequired
155
	 *
156
	 * @param string $fileId
157
	 * @param string $access_token
158
	 * @return Http\Response
159
	 */
160
	public function getFile($fileId,
161
							$access_token) {
162
		list($fileId, , $version) = Helper::parseFileId($fileId);
163
164
		$wopi = $this->wopiMapper->getPathForToken($access_token);
165
166
		if ((int)$fileId !== $wopi->getFileid()) {
167
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
168
		}
169
170
		try {
171
			/** @var File $file */
172
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
173
			$file = $userFolder->getById($fileId)[0];
174
			\OC_User::setIncognitoMode(true);
175
			if ($version !== '0') {
176
				$view = new View('/' . $wopi->getOwnerUid() . '/files');
177
				$relPath = $view->getRelativePath($file->getPath());
178
				$versionPath = '/files_versions/' . $relPath . '.v' . $version;
179
				$view = new View('/' . $wopi->getOwnerUid());
180
				if ($view->file_exists($versionPath)){
181
					$response = new StreamResponse($view->fopen($versionPath, 'rb'));
182
				}
183
				else {
184
					$response->setStatus(Http::STATUS_NOT_FOUND);
0 ignored issues
show
Bug introduced by
The variable $response seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
185
				}
186
			}
187
			else
188
			{
189
				$response = new StreamResponse($file->fopen('rb'));
190
			}
191
			$response->addHeader('Content-Disposition', 'attachment');
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
192
			$response->addHeader('Content-Type', 'application/octet-stream');
193
			return $response;
194
		} catch (\Exception $e) {
195
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
196
		}
197
	}
198
199
	/**
200
	 * Given an access token and a fileId, replaces the files with the request body.
201
	 * Expects a valid token in access_token parameter.
202
	 *
203
	 * @PublicPage
204
	 * @NoCSRFRequired
205
	 *
206
	 * @param string $fileId
207
	 * @param string $access_token
208
	 * @return JSONResponse
209
	 */
210
	public function putFile($fileId,
211
							$access_token) {
212
		list($fileId, , $version) = Helper::parseFileId($fileId);
0 ignored issues
show
Unused Code introduced by
The assignment to $version is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
213
		$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
214
215
		$wopi = $this->wopiMapper->getPathForToken($access_token);
216
		if (!$wopi->getCanwrite()) {
217
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
218
		}
219
220
		try {
221
			/** @var File $file */
222
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
223
			$file = $userFolder->getById($fileId)[0];
224
225
			if ($isPutRelative) {
226
				// the new file needs to be installed in the current user dir
227
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
228
				$file = $userFolder->getById($fileId)[0];
229
230
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
231
				$suggested = iconv('utf-7', 'utf-8', $suggested);
232
233
				$path = '';
234
				if ($suggested[0] === '.') {
235
					$path = dirname($file->getPath()) . '/New File' . $suggested;
236
				}
237
				else if ($suggested[0] !== '/') {
238
					$path = dirname($file->getPath()) . '/' . $suggested;
239
				}
240
				else {
241
					$path = $userFolder->getPath() . $suggested;
242
				}
243
244
				if ($path === '') {
245
					return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('status' =>...nnot create the file'); (array<string,string>) is incompatible with the return type documented by OCA\Richdocuments\Contro...WopiController::putFile of type OCP\AppFramework\Http\JSONResponse.

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...
246
						'status' => 'error',
247
						'message' => 'Cannot create the file'
248
					);
249
				}
250
251
				$root = \OC::$server->getRootFolder();
252
253
				// create the folder first
254
				if (!$root->nodeExists(dirname($path))) {
255
					$root->newFolder(dirname($path));
256
				}
257
258
				// create a unique new file
259
				$path = $root->getNonExistingName($path);
260
				$root->newFile($path);
261
				$file = $root->get($path);
262
			}
263
			else {
264
				$wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp');
265
				if (!is_null($wopiHeaderTime) && $wopiHeaderTime != Helper::toISO8601($file->getMTime())) {
266
					\OC::$server->getLogger()->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [
267
						'headerTime' => $wopiHeaderTime,
268
						'storageTime' => Helper::toISO8601($file->getMtime())
269
					]);
270
					// Tell WOPI client about this conflict.
271
					return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT);
272
				}
273
			}
274
275
			$content = fopen('php://input', 'rb');
276
			// Setup the FS which is needed to emit hooks (versioning).
277
			\OC_Util::tearDownFS();
278
			if (!$isPutRelative) {
279
				\OC_Util::setupFS($wopi->getOwnerUid());
280
			} else {
281
				\OC_Util::setupFS($wopi->getEditorUid());
282
			}
283
284
			// Set the user to register the change under his name
285
			$editor = \OC::$server->getUserManager()->get($wopi->getEditorUid());
286
			if (!is_null($editor)) {
287
				\OC::$server->getUserSession()->setUser($editor);
288
			}
289
290
			$file->putContent($content);
291
292
			if ($isPutRelative) {
293
				// generate a token for the new file (the user still has to be
294
				// logged in)
295
				$serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost();
0 ignored issues
show
Unused Code introduced by
$serverHost 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...
296
				list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
297
298
				$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
299
				$url = \OC::$server->getURLGenerator()->getAbsoluteURL($wopi);
300
301
				return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
302
			}
303
			else {
304
				return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMtime())]);
305
			}
306
		} catch (\Exception $e) {
307
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
308
		}
309
	}
310
311
	/**
312
	 * Given an access token and a fileId, replaces the files with the request body.
313
	 * Expects a valid token in access_token parameter.
314
	 * Just actually routes to the PutFile, the implementation of PutFile
315
	 * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body.
316
	 *
317
	 * @PublicPage
318
	 * @NoCSRFRequired
319
	 *
320
	 * @param string $fileId
321
	 * @param string $access_token
322
	 * @return JSONResponse
323
	 */
324
	public function putRelativeFile($fileId,
325
					$access_token) {
326
		return $this->putFile($fileId, $access_token);
327
	}
328
}
329