Completed
Push — master ( 23419f...3c3a4e )
by Julius
13s queued 10s
created

WopiController::putRelativeFile()   F

Complexity

Conditions 16
Paths 399

Size

Total Lines 114

Duplication

Lines 7
Ratio 6.14 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 0
Metric Value
dl 7
loc 114
ccs 0
cts 82
cp 0
rs 1.7966
c 0
b 0
f 0
cc 16
nc 399
nop 2
crap 272

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\Service\UserScopeService;
29
use OCA\Richdocuments\TemplateManager;
30
use OCA\Richdocuments\TokenManager;
31
use OCA\Richdocuments\Helper;
32
use OCP\AppFramework\Controller;
33
use OCP\AppFramework\Db\DoesNotExistException;
34
use OCP\AppFramework\Http;
35
use OCP\AppFramework\Http\JSONResponse;
36
use OCP\Files\File;
37
use OCP\Files\Folder;
38
use OCP\Files\GenericFileException;
39
use OCP\Files\InvalidPathException;
40
use OCP\Files\IRootFolder;
41
use OCP\Files\Node;
42
use OCP\Files\NotFoundException;
43
use OCP\Files\NotPermittedException;
44
use OCP\IConfig;
45
use OCP\ILogger;
46
use OCP\IRequest;
47
use OCP\IURLGenerator;
48
use OCP\AppFramework\Http\StreamResponse;
49
use OCP\IUserManager;
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 TemplateManager */
72
	private $templateManager;
73
	/** @var IManager */
74
	private $shareManager;
75
	/** @var UserScopeService */
76
	private $userScopeService;
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 TemplateManager $templateManager
92
	 */
93 View Code Duplication
	public function __construct(
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...
94
		$appName,
95
		IRequest $request,
96
		IRootFolder $rootFolder,
97
		IURLGenerator $urlGenerator,
98
		IConfig $config,
99
		AppConfig $appConfig,
100
		TokenManager $tokenManager,
101
		IUserManager $userManager,
102
		WopiMapper $wopiMapper,
103
		ILogger $logger,
104
		TemplateManager $templateManager,
105
		IManager $shareManager,
106
		UserScopeService $userScopeService
107
	) {
108
		parent::__construct($appName, $request);
109
		$this->rootFolder = $rootFolder;
110
		$this->urlGenerator = $urlGenerator;
111
		$this->config = $config;
112
		$this->appConfig = $appConfig;
113
		$this->tokenManager = $tokenManager;
114
		$this->userManager = $userManager;
115
		$this->wopiMapper = $wopiMapper;
116
		$this->logger = $logger;
117
		$this->templateManager = $templateManager;
118
		$this->shareManager = $shareManager;
119
		$this->userScopeService = $userScopeService;
120
	}
121
122
	/**
123
	 * Returns general info about a file.
124
	 *
125
	 * @NoAdminRequired
126
	 * @NoCSRFRequired
127
	 * @PublicPage
128
	 *
129
	 * @param string $fileId
130
	 * @param string $access_token
131
	 * @return JSONResponse
132
	 * @throws InvalidPathException
133
	 * @throws NotFoundException
134
	 */
135
	public function checkFileInfo($fileId, $access_token) {
136
		try {
137
			list($fileId, , $version) = Helper::parseFileId($fileId);
138
139
			$wopi = $this->wopiMapper->getWopiForToken($access_token);
140
			if ($wopi->isTemplateToken()) {
141
				$this->templateManager->setUserId($wopi->getOwnerUid());
142
				$file = $this->templateManager->get($wopi->getFileid());
143
			} else {
144
				$file = $this->getFileForWopiToken($wopi);
145
			}
146
			if(!($file instanceof File)) {
147
				throw new NotFoundException('No valid file found for ' . $fileId);
148
			}
149
		} catch (NotFoundException $e) {
150
			$this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']);
151
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
152
		} catch (DoesNotExistException $e) {
153
			$this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']);
154
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
155
		} catch (\Exception $e) {
156
			$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...
157
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
158
		}
159
160
		$isPublic = $wopi->getEditorUid() === null;
161
		$guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8);
162
		$user = $this->userManager->get($wopi->getEditorUid());
163
		$userDisplayName = $user !== null && !$isPublic ? $user->getDisplayName() : $wopi->getGuestDisplayname();
164
		$response = [
165
			'BaseFileName' => $file->getName(),
166
			'Size' => $file->getSize(),
167
			'Version' => $version,
168
			'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId,
169
			'OwnerId' => $wopi->getOwnerUid(),
170
			'UserFriendlyName' => $userDisplayName,
171
			'UserExtraInfo' => [
172
			],
173
			'UserCanWrite' => $wopi->getCanwrite(),
174
			'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() || $isPublic,
175
			'PostMessageOrigin' => $wopi->getServerHost(),
176
			'LastModifiedTime' => Helper::toISO8601($file->getMTime()),
177
			'SupportsRename' => true,
178
			'UserCanRename' => !$isPublic,
179
			'EnableInsertRemoteImage' => true,
180
			'EnableShare' => true,
181
			'HideUserList' => 'desktop',
182
			'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...
183
			'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...
184
			'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...
185
			'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...
186
			'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...
187
			'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...
188
		];
189
190
		if ($wopi->isTemplateToken()) {
191
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
192
			$file = $userFolder->getById($wopi->getTemplateDestination())[0];
193
			$response['TemplateSaveAs'] = $file->getName();
194
		}
195
196
		if ($this->shouldWatermark($isPublic, $wopi->getEditorUid(), $fileId, $wopi)) {
197
			$replacements = [
198
				'userId' => $wopi->getEditorUid(),
199
				'date' => (new \DateTime())->format('Y-m-d H:i:s'),
200
				'themingName' => \OC::$server->getThemingDefaults()->getName(),
201
202
			];
203
			$watermarkTemplate = $this->appConfig->getAppValue('watermark_text');
204
			$response['WatermarkText'] = preg_replace_callback('/{(.+?)}/', function($matches) use ($replacements)
205
			{
206
				return $replacements[$matches[1]];
207
			}, $watermarkTemplate);
208
		}
209
210
		$user = $this->userManager->get($wopi->getEditorUid());
211
		if($user !== null && $user->getAvatarImage(32) !== null) {
212
			$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
213
		}
214
215
		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...
216
			$response = $this->setFederationFileInfo($wopi, $response);
217
		}
218
219
		return new JSONResponse($response);
220
	}
221
222
	private function setFederationFileInfo($wopi, $response) {
223
		$remoteUserId = $wopi->getGuestDisplayname();
224
		$cloudID = \OC::$server->getCloudIdManager()->resolveCloudId($remoteUserId);
225
		$response['UserFriendlyName'] = $cloudID->getDisplayId();
226
		$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => 32]);
227
		$cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId());
228
		$addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']);
229
		foreach ($addressBookEntries as $entry) {
230
			if (isset($entry['CLOUD'])) {
231
				foreach ($entry['CLOUD'] as $cloudID) {
232
					if ($cloudID === $cleanCloudId) {
233
						$response['UserFriendlyName'] = $entry['FN'];
234
						break;
235
					}
236
				}
237
			}
238
		}
239
		return $response;
240
	}
241
242
	private function shouldWatermark($isPublic, $userId, $fileId, Wopi $wopi) {
243
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
244
			return false;
245
		}
246
247
		if ($isPublic) {
248
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
249
				return true;
250
			}
251 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...
252
				return true;
253
			}
254
			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...
255
				return true;
256
			}
257 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...
258
				$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
259
				$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
260
				foreach ($fileTags as $tagId) {
261
					if (in_array($tagId, $tags, true)) {
262
						return true;
263
					}
264
				}
265
			}
266
		} else {
267
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
268
				$files = $this->rootFolder->getUserFolder($userId)->getById($fileId);
269
				if (count($files) !== 0 && $files[0]->getOwner()->getUID() !== $userId) {
270
					return true;
271
				}
272
			}
273 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...
274
				return true;
275
			}
276
		}
277
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
278
			$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
279
			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...
280
				if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) {
281
					return true;
282
				}
283
			}
284
		}
285 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...
286
			$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
287
			$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
288
			foreach ($fileTags as $tagId) {
289
				if (in_array($tagId, $tags, true)) {
290
					return true;
291
				}
292
			}
293
		}
294
295
		return false;
296
	}
297
298
	/**
299
	 * Given an access token and a fileId, returns the contents of the file.
300
	 * Expects a valid token in access_token parameter.
301
	 *
302
	 * @PublicPage
303
	 * @NoCSRFRequired
304
	 *
305
	 * @param string $fileId
306
	 * @param string $access_token
307
	 * @return Http\Response
308
	 * @throws DoesNotExistException
309
	 * @throws NotFoundException
310
	 * @throws NotPermittedException
311
	 */
312
	public function getFile($fileId,
313
							$access_token) {
314
		list($fileId, , $version) = Helper::parseFileId($fileId);
315
316
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
317
318
		if ((int)$fileId !== $wopi->getFileid()) {
319
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
320
		}
321
322
		// Template is just returned as there is no version logic
323
		if ($wopi->isTemplateToken()) {
324
			$this->templateManager->setUserId($wopi->getOwnerUid());
325
			$file = $this->templateManager->get($wopi->getFileid());
326
			$response = new StreamResponse($file->fopen('rb'));
327
			$response->addHeader('Content-Disposition', 'attachment');
328
			$response->addHeader('Content-Type', 'application/octet-stream');
329
			return $response;
330
		}
331
332
		try {
333
			/** @var File $file */
334
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
335
			$file = $userFolder->getById($fileId)[0];
336
			\OC_User::setIncognitoMode(true);
337
			if ($version !== '0') {
338
				$view = new View('/' . $wopi->getOwnerUid() . '/files');
339
				$relPath = $view->getRelativePath($file->getPath());
340
				$versionPath = '/files_versions/' . $relPath . '.v' . $version;
341
				$view = new View('/' . $wopi->getOwnerUid());
342
				if ($view->file_exists($versionPath)){
343
					$response = new StreamResponse($view->fopen($versionPath, 'rb'));
344
				}
345
				else {
346
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
347
				}
348
			}
349
			else
350
			{
351
				$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...
352
			}
353
			$response->addHeader('Content-Disposition', 'attachment');
354
			$response->addHeader('Content-Type', 'application/octet-stream');
355
			return $response;
356
		} catch (\Exception $e) {
357
			$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...
358
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
359
		}
360
	}
361
362
	/**
363
	 * Given an access token and a fileId, replaces the files with the request body.
364
	 * Expects a valid token in access_token parameter.
365
	 *
366
	 * @PublicPage
367
	 * @NoCSRFRequired
368
	 *
369
	 * @param string $fileId
370
	 * @param string $access_token
371
	 * @return JSONResponse
372
	 * @throws DoesNotExistException
373
	 */
374
	public function putFile($fileId,
375
							$access_token) {
376
		list($fileId, ,) = Helper::parseFileId($fileId);
377
		$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
378
		$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...
379
380
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
381
		if (!$wopi->getCanwrite()) {
382
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
383
		}
384
385
		try {
386
			if ($isPutRelative) {
387
				// the new file needs to be installed in the current user dir
388
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
389
				$file = $userFolder->getById($fileId)[0];
390
391
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
392
				$suggested = iconv('utf-7', 'utf-8', $suggested);
393
394 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...
395
					$path = dirname($file->getPath()) . '/New File' . $suggested;
396
				}
397
				else if ($suggested[0] !== '/') {
398
					$path = dirname($file->getPath()) . '/' . $suggested;
399
				}
400
				else {
401
					$path = $userFolder->getPath() . $suggested;
402
				}
403
404
				if ($path === '') {
405
					return new JSONResponse([
406
						'status' => 'error',
407
						'message' => 'Cannot create the file'
408
					]);
409
				}
410
411
				// create the folder first
412
				if (!$this->rootFolder->nodeExists(dirname($path))) {
413
					$this->rootFolder->newFolder(dirname($path));
414
				}
415
416
				// create a unique new file
417
				$path = $this->rootFolder->getNonExistingName($path);
418
				$this->rootFolder->newFile($path);
419
				$file = $this->rootFolder->get($path);
420
			} else {
421
				$file = $this->getFileForWopiToken($wopi);
422
				$wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp');
423
				if ($wopiHeaderTime !== null && $wopiHeaderTime !== Helper::toISO8601($file->getMTime())) {
424
					$this->logger->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [
425
						'headerTime' => $wopiHeaderTime,
426
						'storageTime' => Helper::toISO8601($file->getMTime())
427
					]);
428
					// Tell WOPI client about this conflict.
429
					return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT);
430
				}
431
			}
432
433
			$content = fopen('php://input', 'rb');
434
435
			// Set the user to register the change under his name
436
			$this->userScopeService->setUserScope($wopi->getEditorUid());
437
438
			try {
439
				$this->retryOperation(function () use ($file, $content){
440
					return $file->putContent($content);
441
				});
442
			} catch (LockedException $e) {
443
				$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...
444
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
445
			}
446
447
			if ($isPutRelative) {
448
				// generate a token for the new file (the user still has to be
449
				// logged in)
450
				list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
451
452
				$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
453
				$url = $this->urlGenerator->getAbsoluteURL($wopi);
454
455
				return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
456
			}
457
458
			return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMTime())]);
459
		} catch (\Exception $e) {
460
			$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...
461
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
462
		}
463
	}
464
465
	/**
466
	 * Given an access token and a fileId, replaces the files with the request body.
467
	 * Expects a valid token in access_token parameter.
468
	 * Just actually routes to the PutFile, the implementation of PutFile
469
	 * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body.
470
	 *
471
	 * @PublicPage
472
	 * @NoCSRFRequired
473
	 *
474
	 * @param string $fileId
475
	 * @param string $access_token
476
	 * @return JSONResponse
477
	 * @throws DoesNotExistException
478
	 */
479
	public function putRelativeFile($fileId,
480
					$access_token) {
481
		list($fileId, ,) = Helper::parseFileId($fileId);
482
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
483
		$isRenameFile = ($this->request->getHeader('X-WOPI-Override') === 'RENAME_FILE');
484
485
		if (!$wopi->getCanwrite()) {
486
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
487
		}
488
489
		// Unless the editor is empty (public link) we modify the files as the current editor
490
		$editor = $wopi->getEditorUid();
491
		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...
492
			$editor = $wopi->getOwnerUid();
493
		}
494
495
		try {
496
			// the new file needs to be installed in the current user dir
497
			$userFolder = $this->rootFolder->getUserFolder($editor);
498
499
			if ($wopi->isTemplateToken()) {
500
				$this->templateManager->setUserId($wopi->getOwnerUid());
501
				$file = $userFolder->getById($wopi->getTemplateDestination())[0];
502
			} else if ($isRenameFile) {
503
				// the new file needs to be installed in the current user dir
504
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
505
				$file = $userFolder->getById($fileId)[0];
506
507
				$suggested = $this->request->getHeader('X-WOPI-RequestedName');
508
509
				$suggested = iconv('utf-7', 'utf-8', $suggested) . '.' . $file->getExtension();
510
511
				if (strpos($suggested, '.') === 0) {
512
					$path = dirname($file->getPath()) . '/New File' . $suggested;
513
				}
514
				else if (strpos($suggested, '/') !== 0) {
515
					$path = dirname($file->getPath()) . '/' . $suggested;
516
				}
517
				else {
518
					$path = $userFolder->getPath() . $suggested;
519
				}
520
521
				if ($path === '') {
522
					return new JSONResponse([
523
						'status' => 'error',
524
						'message' => 'Cannot rename the file'
525
					]);
526
				}
527
528
				// create the folder first
529
				if (!$this->rootFolder->nodeExists(dirname($path))) {
530
					$this->rootFolder->newFolder(dirname($path));
531
				}
532
533
				// create a unique new file
534
				$path = $this->rootFolder->getNonExistingName($path);
535
				$file = $file->move($path);
536
			} else {
537
				$file = $userFolder->getById($fileId)[0];
538
539
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
540
				$suggested = iconv('utf-7', 'utf-8', $suggested);
541
542 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...
543
					$path = dirname($file->getPath()) . '/New File' . $suggested;
544
				} else if ($suggested[0] !== '/') {
545
					$path = dirname($file->getPath()) . '/' . $suggested;
546
				} else {
547
					$path = $userFolder->getPath() . $suggested;
548
				}
549
550
				if ($path === '') {
551
					return new JSONResponse([
552
						'status' => 'error',
553
						'message' => 'Cannot create the file'
554
					]);
555
				}
556
557
				// create the folder first
558
				if (!$this->rootFolder->nodeExists(dirname($path))) {
559
					$this->rootFolder->newFolder(dirname($path));
560
				}
561
562
				// create a unique new file
563
				$path = $this->rootFolder->getNonExistingName($path);
564
				$file = $this->rootFolder->newFile($path);
565
			}
566
567
			$content = fopen('php://input', 'rb');
568
569
			// Set the user to register the change under his name
570
			$this->userScopeService->setUserScope($wopi->getEditorUid());
571
572
			try {
573
				$this->retryOperation(function () use ($file, $content){
574
					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...
575
				});
576
			} catch (LockedException $e) {
577
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
578
			}
579
580
			// generate a token for the new file (the user still has to be
581
			// logged in)
582
			list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
583
584
			$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
585
			$url = $this->urlGenerator->getAbsoluteURL($wopi);
586
587
			return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
588
		} catch (\Exception $e) {
589
			$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...
590
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
591
		}
592
	}
593
594
	/**
595
	 * Retry operation if a LockedException occurred
596
	 * Other exceptions will still be thrown
597
	 * @param callable $operation
598
	 * @throws LockedException
599
	 * @throws GenericFileException
600
	 */
601
	private function retryOperation(callable $operation) {
602
		for ($i = 0; $i < 5; $i++) {
603
			try {
604
				if ($operation() !== false) {
605
					return;
606
				}
607
			} catch (LockedException $e) {
608
				if ($i === 4) {
609
					throw $e;
610
				}
611
				usleep(500000);
612
			}
613
		}
614
		throw new GenericFileException('Operation failed after multiple retries');
615
	}
616
617
	/**
618
	 * @param Wopi $wopi
619
	 * @return File|Folder|Node|null
620
	 * @throws NotFoundException
621
	 * @throws ShareNotFound
622
	 */
623
	private function getFileForWopiToken(Wopi $wopi) {
624
		$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...
625
626
		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...
627
			$share = $this->shareManager->getShareByToken($wopi->getEditorUid());
628
			$node = $share->getNode();
629
			if ($node instanceof Folder) {
630
				$file = $node->getById($wopi->getFileid())[0];
631
			} else {
632
				$file = $node;
633
			}
634
		} else {
635
			// Unless the editor is empty (public link) we modify the files as the current editor
636
			// TODO: add related share token to the wopi table so we can obtain the
637
			$editor = $wopi->getEditorUid();
638
639
			// Use the actual file owner no editor is available
640
			if ($editor === null || $wopi->getGuestDisplayname() === null) {
641
				$editor = $wopi->getOwnerUid();
642
			}
643
644
			$userFolder = $this->rootFolder->getUserFolder($editor);
645
			$files = $userFolder->getById($wopi->getFileid());
646
			if (isset($files[0]) && $files[0] instanceof File) {
647
				$file = $files[0];
648
			} else {
649
				throw new NotFoundException('No valid file found for wopi token');
650
			}
651
		}
652
		return $file;
653
	}
654
}
655