Completed
Pull Request — master (#694)
by Julius
03:34
created

WopiController::putRelativeFile()   F

Complexity

Conditions 17
Paths 633

Size

Total Lines 117

Duplication

Lines 9
Ratio 7.69 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 0
Metric Value
dl 9
loc 117
ccs 0
cts 86
cp 0
rs 1.2477
c 0
b 0
f 0
cc 17
nc 633
nop 2
crap 306

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\Wopi;
26
use OCA\Richdocuments\AppConfig;
27
use OCA\Richdocuments\Db\WopiMapper;
28
use OCA\Richdocuments\TemplateManager;
29
use OCA\Richdocuments\TokenManager;
30
use OCA\Richdocuments\Helper;
31
use OCP\AppFramework\Controller;
32
use OCP\AppFramework\Db\DoesNotExistException;
33
use OCP\AppFramework\Http;
34
use OCP\AppFramework\Http\JSONResponse;
35
use OCP\Files\File;
36
use OCP\Files\Folder;
37
use OCP\Files\GenericFileException;
38
use OCP\Files\InvalidPathException;
39
use OCP\Files\IRootFolder;
40
use OCP\Files\Node;
41
use OCP\Files\NotFoundException;
42
use OCP\Files\NotPermittedException;
43
use OCP\IConfig;
44
use OCP\ILogger;
45
use OCP\IRequest;
46
use OCP\IURLGenerator;
47
use OCP\AppFramework\Http\StreamResponse;
48
use OCP\IUserManager;
49
use OCP\IUserSession;
50
use OCP\Lock\LockedException;
51
use OCP\Share\Exceptions\ShareNotFound;
52
use OCP\Share\IManager;
53
54
class WopiController extends Controller {
55
	/** @var IRootFolder */
56
	private $rootFolder;
57
	/** @var IURLGenerator */
58
	private $urlGenerator;
59
	/** @var IConfig */
60
	private $config;
61
	/** @var AppConfig */
62
	private $appConfig;
63
	/** @var TokenManager */
64
	private $tokenManager;
65
	/** @var IUserManager */
66
	private $userManager;
67
	/** @var WopiMapper */
68
	private $wopiMapper;
69
	/** @var ILogger */
70
	private $logger;
71
	/** @var IUserSession */
72
	private $userSession;
73
	/** @var TemplateManager */
74
	private $templateManager;
75
	/** @var IManager */
76
	private $shareManager;
77
78
	// Signifies LOOL that document has been changed externally in this storage
79
	const LOOL_STATUS_DOC_CHANGED = 1010;
80
81
	/**
82
	 * @param string $appName
83
	 * @param IRequest $request
84
	 * @param IRootFolder $rootFolder
85
	 * @param IURLGenerator $urlGenerator
86
	 * @param IConfig $config
87
	 * @param TokenManager $tokenManager
88
	 * @param IUserManager $userManager
89
	 * @param WopiMapper $wopiMapper
90
	 * @param ILogger $logger
91
	 * @param IUserSession $userSession
92
	 * @param TemplateManager $templateManager
93
	 */
94
	public function __construct(
95
		$appName,
96
		IRequest $request,
97
		IRootFolder $rootFolder,
98
		IURLGenerator $urlGenerator,
99
		IConfig $config,
100
		AppConfig $appConfig,
101
		TokenManager $tokenManager,
102
		IUserManager $userManager,
103
		WopiMapper $wopiMapper,
104
		ILogger $logger,
105
		IUserSession $userSession,
106
		TemplateManager $templateManager,
107
		IManager $shareManager
108
	) {
109
		parent::__construct($appName, $request);
110
		$this->rootFolder = $rootFolder;
111
		$this->urlGenerator = $urlGenerator;
112
		$this->config = $config;
113
		$this->appConfig = $appConfig;
114
		$this->tokenManager = $tokenManager;
115
		$this->userManager = $userManager;
116
		$this->wopiMapper = $wopiMapper;
117
		$this->logger = $logger;
118
		$this->userSession = $userSession;
119
		$this->templateManager = $templateManager;
120
		$this->shareManager = $shareManager;
121
	}
122
123
	/**
124
	 * Returns general info about a file.
125
	 *
126
	 * @NoAdminRequired
127
	 * @NoCSRFRequired
128
	 * @PublicPage
129
	 *
130
	 * @param string $fileId
131
	 * @param string $access_token
132
	 * @return JSONResponse
133
	 * @throws InvalidPathException
134
	 * @throws NotFoundException
135
	 */
136
	public function checkFileInfo($fileId, $access_token) {
137
		try {
138
			list($fileId, , $version) = Helper::parseFileId($fileId);
139
140
			$wopi = $this->wopiMapper->getWopiForToken($access_token);
141
			if ($wopi->isTemplateToken()) {
142
				$this->templateManager->setUserId($wopi->getOwnerUid());
143
				$file = $this->templateManager->get($wopi->getFileid());
144
			} else {
145
				$file = $this->getFileForWopiToken($wopi);
146
			}
147
			if(!($file instanceof File)) {
148
				throw new NotFoundException('No valid file found for ' . $fileId);
149
			}
150
		} catch (NotFoundException $e) {
151
			$this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']);
152
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
153
		} catch (DoesNotExistException $e) {
154
			$this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']);
155
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
156
		} catch (\Exception $e) {
157
			$this->logger->logException($e, ['app' => 'richdocuments']);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
158
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
159
		}
160
161
		$isPublic = $wopi->getEditorUid() === null;
162
		$guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8);
163
		$user = $this->userManager->get($wopi->getEditorUid());
164
		$userDisplayName = $user !== null && !$isPublic ? $user->getDisplayName() : $wopi->getGuestDisplayname();
165
		$response = [
166
			'BaseFileName' => $file->getName(),
167
			'Size' => $file->getSize(),
168
			'Version' => $version,
169
			'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId,
170
			'OwnerId' => $wopi->getOwnerUid(),
171
			'UserFriendlyName' => $userDisplayName,
172
			'UserExtraInfo' => [
173
			],
174
			'UserCanWrite' => $wopi->getCanwrite(),
175
			'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() || $isPublic,
176
			'PostMessageOrigin' => $wopi->getServerHost(),
177
			'LastModifiedTime' => Helper::toISO8601($file->getMTime()),
178
			'SupportsRename' => true,
179
			'UserCanRename' => !$isPublic,
180
			'EnableInsertRemoteImage' => true,
181
			'EnableShare' => true,
182
			'HideUserList' => 'desktop',
183
			'DisablePrint' => $wopi->getHideDownload(),
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
184
			'DisableExport' => $wopi->getHideDownload(),
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
185
			'DisableCopy' => $wopi->getHideDownload(),
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
186
			'HideExportOption' => $wopi->getHideDownload(),
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
187
			'HidePrintOption' => $wopi->getHideDownload(),
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
188
			'DownloadAsPostMessage' => $wopi->getDirect(),
0 ignored issues
show
Documentation Bug introduced by
The method getDirect does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
189
		];
190
191
		if ($wopi->isTemplateToken()) {
192
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
193
			$file = $userFolder->getById($wopi->getTemplateDestination())[0];
194
			$response['TemplateSaveAs'] = $file->getName();
195
		}
196
197
		if ($this->shouldWatermark($isPublic, $wopi->getEditorUid(), $fileId, $wopi)) {
198
			$replacements = [
199
				'userId' => $wopi->getEditorUid(),
200
				'date' => (new \DateTime())->format('Y-m-d H:i:s'),
201
				'themingName' => \OC::$server->getThemingDefaults()->getName(),
202
203
			];
204
			$watermarkTemplate = $this->appConfig->getAppValue('watermark_text');
205
			$response['WatermarkText'] = preg_replace_callback('/{(.+?)}/', function($matches) use ($replacements)
206
			{
207
				return $replacements[$matches[1]];
208
			}, $watermarkTemplate);
209
		}
210
211
		$user = $this->userManager->get($wopi->getEditorUid());
212
		if($user !== null && $user->getAvatarImage(32) !== null) {
213
			$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
214
		}
215
216
		if ($wopi->getRemoteServer() !== '') {
0 ignored issues
show
Documentation Bug introduced by
The method getRemoteServer does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
217
			$response = $this->setFederationFileInfo($wopi, $response);
218
		}
219
220
		return new JSONResponse($response);
221
	}
222
223
	private function setFederationFileInfo($wopi, $response) {
224
		$remoteUserId = $wopi->getGuestDisplayname();
225
		$cloudID = \OC::$server->getCloudIdManager()->resolveCloudId($remoteUserId);
226
		$response['UserFriendlyName'] = $cloudID->getDisplayId();
227
		$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => 32]);
228
		$cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId());
229
		$addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']);
230
		foreach ($addressBookEntries as $entry) {
231
			if (isset($entry['CLOUD'])) {
232
				foreach ($entry['CLOUD'] as $cloudID) {
233
					if ($cloudID === $cleanCloudId) {
234
						$response['UserFriendlyName'] = $entry['FN'];
235
						break;
236
					}
237
				}
238
			}
239
		}
240
		return $response;
241
	}
242
243
	private function shouldWatermark($isPublic, $userId, $fileId, Wopi $wopi) {
244
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
245
			return false;
246
		}
247
248
		if ($isPublic) {
249
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
250
				return true;
251
			}
252 View Code Duplication
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
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...
253
				return true;
254
			}
255
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkSecure', 'no') === 'yes' && $wopi->getHideDownload()) {
0 ignored issues
show
Documentation Bug introduced by
The method getHideDownload does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
256
				return true;
257
			}
258 View Code Duplication
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkTags', 'no') === 'yes') {
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...
259
				$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
260
				$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
261
				foreach ($fileTags as $tagId) {
262
					if (in_array($tagId, $tags, true)) {
263
						return true;
264
					}
265
				}
266
			}
267
		} else {
268
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
269
				$files = $this->rootFolder->getUserFolder($userId)->getById($fileId);
270
				if (count($files) !== 0 && $files[0]->getOwner()->getUID() !== $userId) {
271
					return true;
272
				}
273
			}
274 View Code Duplication
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
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...
275
				return true;
276
			}
277
		}
278
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
279
			$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
280
			foreach ($groups as $group) {
0 ignored issues
show
Bug introduced by
The expression $groups of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
281
				if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) {
282
					return true;
283
				}
284
			}
285
		}
286 View Code Duplication
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allTags', 'no') === 'yes') {
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
			$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
288
			$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
289
			foreach ($fileTags as $tagId) {
290
				if (in_array($tagId, $tags, true)) {
291
					return true;
292
				}
293
			}
294
		}
295
296
		return false;
297
	}
298
299
	/**
300
	 * Given an access token and a fileId, returns the contents of the file.
301
	 * Expects a valid token in access_token parameter.
302
	 *
303
	 * @PublicPage
304
	 * @NoCSRFRequired
305
	 *
306
	 * @param string $fileId
307
	 * @param string $access_token
308
	 * @return Http\Response
309
	 * @throws DoesNotExistException
310
	 * @throws NotFoundException
311
	 * @throws NotPermittedException
312
	 */
313
	public function getFile($fileId,
314
							$access_token) {
315
		list($fileId, , $version) = Helper::parseFileId($fileId);
316
317
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
318
319
		if ((int)$fileId !== $wopi->getFileid()) {
320
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
321
		}
322
323
		// Template is just returned as there is no version logic
324
		if ($wopi->isTemplateToken()) {
325
			$this->templateManager->setUserId($wopi->getOwnerUid());
326
			$file = $this->templateManager->get($wopi->getFileid());
327
			$response = new StreamResponse($file->fopen('rb'));
328
			$response->addHeader('Content-Disposition', 'attachment');
329
			$response->addHeader('Content-Type', 'application/octet-stream');
330
			return $response;
331
		}
332
333
		try {
334
			/** @var File $file */
335
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
336
			$file = $userFolder->getById($fileId)[0];
337
			\OC_User::setIncognitoMode(true);
338
			if ($version !== '0') {
339
				$view = new View('/' . $wopi->getOwnerUid() . '/files');
340
				$relPath = $view->getRelativePath($file->getPath());
341
				$versionPath = '/files_versions/' . $relPath . '.v' . $version;
342
				$view = new View('/' . $wopi->getOwnerUid());
343
				if ($view->file_exists($versionPath)){
344
					$response = new StreamResponse($view->fopen($versionPath, 'rb'));
345
				}
346
				else {
347
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
348
				}
349
			}
350
			else
351
			{
352
				$response = new StreamResponse($file->fopen('rb'));
0 ignored issues
show
Bug introduced by
The method fopen() does not seem to exist on object<OCP\Files\Node>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
353
			}
354
			$response->addHeader('Content-Disposition', 'attachment');
355
			$response->addHeader('Content-Type', 'application/octet-stream');
356
			return $response;
357
		} catch (\Exception $e) {
358
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'getFile failed']);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
359
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
360
		}
361
	}
362
363
	/**
364
	 * Given an access token and a fileId, replaces the files with the request body.
365
	 * Expects a valid token in access_token parameter.
366
	 *
367
	 * @PublicPage
368
	 * @NoCSRFRequired
369
	 *
370
	 * @param string $fileId
371
	 * @param string $access_token
372
	 * @return JSONResponse
373
	 * @throws DoesNotExistException
374
	 */
375
	public function putFile($fileId,
376
							$access_token) {
377
		list($fileId, ,) = Helper::parseFileId($fileId);
378
		$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
379
		$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...
380
381
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
382
		if (!$wopi->getCanwrite()) {
383
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
384
		}
385
386
		try {
387
			if ($isPutRelative) {
388
				// the new file needs to be installed in the current user dir
389
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
390
				$file = $userFolder->getById($fileId)[0];
391
392
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
393
				$suggested = iconv('utf-7', 'utf-8', $suggested);
394
395 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...
396
					$path = dirname($file->getPath()) . '/New File' . $suggested;
397
				}
398
				else if ($suggested[0] !== '/') {
399
					$path = dirname($file->getPath()) . '/' . $suggested;
400
				}
401
				else {
402
					$path = $userFolder->getPath() . $suggested;
403
				}
404
405
				if ($path === '') {
406
					return new JSONResponse([
407
						'status' => 'error',
408
						'message' => 'Cannot create the file'
409
					]);
410
				}
411
412
				// create the folder first
413
				if (!$this->rootFolder->nodeExists(dirname($path))) {
414
					$this->rootFolder->newFolder(dirname($path));
415
				}
416
417
				// create a unique new file
418
				$path = $this->rootFolder->getNonExistingName($path);
419
				$this->rootFolder->newFile($path);
420
				$file = $this->rootFolder->get($path);
421
			} else {
422
				$file = $this->getFileForWopiToken($wopi);
423
				$wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp');
424
				if ($wopiHeaderTime !== null && $wopiHeaderTime !== Helper::toISO8601($file->getMTime())) {
425
					$this->logger->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [
426
						'headerTime' => $wopiHeaderTime,
427
						'storageTime' => Helper::toISO8601($file->getMTime())
428
					]);
429
					// Tell WOPI client about this conflict.
430
					return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT);
431
				}
432
			}
433
434
			$content = fopen('php://input', 'rb');
435
436
			// Set the user to register the change under his name
437
			$editor = $this->userManager->get($wopi->getEditorUid());
438
			if ($editor !== null) {
439
				$this->userSession->setUser($editor);
440
			}
441
442
			try {
443
				$this->retryOperation(function () use ($file, $content){
444
					return $file->putContent($content);
445
				});
446
			} catch (LockedException $e) {
447
				$this->logger->logException($e);
0 ignored issues
show
Documentation introduced by
$e is of type object<OCP\Lock\LockedException>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
448
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
449
			}
450
451
			if ($isPutRelative) {
452
				// generate a token for the new file (the user still has to be
453
				// logged in)
454
				list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
455
456
				$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
457
				$url = $this->urlGenerator->getAbsoluteURL($wopi);
458
459
				return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
460
			}
461
462
			return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMTime())]);
463
		} catch (\Exception $e) {
464
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'getFile failed']);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
465
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
466
		}
467
	}
468
469
	/**
470
	 * Given an access token and a fileId, replaces the files with the request body.
471
	 * Expects a valid token in access_token parameter.
472
	 * Just actually routes to the PutFile, the implementation of PutFile
473
	 * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body.
474
	 *
475
	 * @PublicPage
476
	 * @NoCSRFRequired
477
	 *
478
	 * @param string $fileId
479
	 * @param string $access_token
480
	 * @return JSONResponse
481
	 * @throws DoesNotExistException
482
	 */
483
	public function putRelativeFile($fileId,
484
					$access_token) {
485
		list($fileId, ,) = Helper::parseFileId($fileId);
486
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
487
		$isRenameFile = ($this->request->getHeader('X-WOPI-Override') === 'RENAME_FILE');
488
489
		if (!$wopi->getCanwrite()) {
490
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
491
		}
492
493
		// Unless the editor is empty (public link) we modify the files as the current editor
494
		$editor = $wopi->getEditorUid();
495
		if ($editor === null || $wopi->getRemoteServer() !== '') {
0 ignored issues
show
Documentation Bug introduced by
The method getRemoteServer does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
496
			$editor = $wopi->getOwnerUid();
497
		}
498
499
		try {
500
			// the new file needs to be installed in the current user dir
501
			$userFolder = $this->rootFolder->getUserFolder($editor);
502
503
			if ($wopi->isTemplateToken()) {
504
				$this->templateManager->setUserId($wopi->getOwnerUid());
505
				$file = $userFolder->getById($wopi->getTemplateDestination())[0];
506
			} else if ($isRenameFile) {
507
				// the new file needs to be installed in the current user dir
508
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
509
				$file = $userFolder->getById($fileId)[0];
510
511
				$suggested = $this->request->getHeader('X-WOPI-RequestedName');
512
513
				$suggested = iconv('utf-7', 'utf-8', $suggested) . '.' . $file->getExtension();
514
515
				if (strpos($suggested, '.') === 0) {
516
					$path = dirname($file->getPath()) . '/New File' . $suggested;
517
				}
518
				else if (strpos($suggested, '/') !== 0) {
519
					$path = dirname($file->getPath()) . '/' . $suggested;
520
				}
521
				else {
522
					$path = $userFolder->getPath() . $suggested;
523
				}
524
525
				if ($path === '') {
526
					return new JSONResponse([
527
						'status' => 'error',
528
						'message' => 'Cannot rename the file'
529
					]);
530
				}
531
532
				// create the folder first
533
				if (!$this->rootFolder->nodeExists(dirname($path))) {
534
					$this->rootFolder->newFolder(dirname($path));
535
				}
536
537
				// create a unique new file
538
				$path = $this->rootFolder->getNonExistingName($path);
539
				$file = $file->move($path);
540
			} else {
541
				$file = $userFolder->getById($fileId)[0];
542
543
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
544
				$suggested = iconv('utf-7', 'utf-8', $suggested);
545
546 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...
547
					$path = dirname($file->getPath()) . '/New File' . $suggested;
548
				} else if ($suggested[0] !== '/') {
549
					$path = dirname($file->getPath()) . '/' . $suggested;
550
				} else {
551
					$path = $userFolder->getPath() . $suggested;
552
				}
553
554
				if ($path === '') {
555
					return new JSONResponse([
556
						'status' => 'error',
557
						'message' => 'Cannot create the file'
558
					]);
559
				}
560
561
				// create the folder first
562
				if (!$this->rootFolder->nodeExists(dirname($path))) {
563
					$this->rootFolder->newFolder(dirname($path));
564
				}
565
566
				// create a unique new file
567
				$path = $this->rootFolder->getNonExistingName($path);
568
				$file = $this->rootFolder->newFile($path);
569
			}
570
571
			$content = fopen('php://input', 'rb');
572
573
			// Set the user to register the change under his name
574
			$editor = $this->userManager->get($wopi->getEditorUid());
575
			if ($editor !== null) {
576
				$this->userSession->setUser($editor);
577
			}
578
579
			try {
580
				$this->retryOperation(function () use ($file, $content){
581
					return $file->putContent($content);
0 ignored issues
show
Bug introduced by
The method putContent() does not seem to exist on object<OCP\Files\Node>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
582
				});
583
			} catch (LockedException $e) {
584
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
585
			}
586
587
			// generate a token for the new file (the user still has to be
588
			// logged in)
589
			list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
590
591
			$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
592
			$url = $this->urlGenerator->getAbsoluteURL($wopi);
593
594
			return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
595
		} catch (\Exception $e) {
596
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'putRelativeFile failed']);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
597
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
598
		}
599
	}
600
601
	/**
602
	 * Retry operation if a LockedException occurred
603
	 * Other exceptions will still be thrown
604
	 * @param callable $operation
605
	 * @throws LockedException
606
	 * @throws GenericFileException
607
	 */
608
	private function retryOperation(callable $operation) {
609
		for ($i = 0; $i < 5; $i++) {
610
			try {
611
				if ($operation() !== false) {
612
					return;
613
				}
614
			} catch (LockedException $e) {
615
				if ($i === 4) {
616
					throw $e;
617
				}
618
				usleep(500000);
619
			}
620
		}
621
		throw new GenericFileException('Operation failed after multiple retries');
622
	}
623
624
	/**
625
	 * @param Wopi $wopi
626
	 * @return File|Folder|Node|null
627
	 * @throws NotFoundException
628
	 * @throws ShareNotFound
629
	 */
630
	private function getFileForWopiToken(Wopi $wopi) {
631
		$file = null;
0 ignored issues
show
Unused Code introduced by
$file 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...
632
633
		if ($wopi->getRemoteServer() !== '') {
0 ignored issues
show
Documentation Bug introduced by
The method getRemoteServer does not exist on object<OCA\Richdocuments\Db\Wopi>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
634
			$share = $this->shareManager->getShareByToken($wopi->getEditorUid());
635
			$node = $share->getNode();
636
			if ($node instanceof Folder) {
637
				$file = $node->getById($wopi->getFileid())[0];
638
			} else {
639
				$file = $node;
640
			}
641
		} else {
642
			// Unless the editor is empty (public link) we modify the files as the current editor
643
			// TODO: add related share token to the wopi table so we can obtain the
644
			$editor = $wopi->getEditorUid();
645
			if ($editor === null) {
646
				$editor = $wopi->getOwnerUid();
647
			}
648
649
			$userFolder = $this->rootFolder->getUserFolder($editor);
650
			$files = $userFolder->getById($wopi->getFileid());
651
			if (isset($files[0]) && $files[0] instanceof File) {
652
				$file = $files[0];
653
			} else {
654
				throw new NotFoundException();
655
			}
656
		}
657
		return $file;
658
	}
659
}
660