Completed
Push — master ( 3c3a4e...2d48a9 )
by Julius
02:29 queued 11s
created

WopiController::getTemplate()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 21
cp 0
rs 9.552
c 0
b 0
f 0
cc 4
nc 8
nop 2
crap 20
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
				return $replacements[$matches[1]];
206
			}, $watermarkTemplate);
207
		}
208
209
		/**
210
		 * New approach for generating files from templates by creating an empty file
211
		 * and providing an URL which returns the actual templyte
212
		 */
213
		if ($wopi->hasTemplateId()) {
214
			$templateUrl = 'index.php/apps/richdocuments/wopi/template/' . $wopi->getTemplateId() . '?access_token=' . $wopi->getToken();
215
			$templateUrl = $this->urlGenerator->getAbsoluteURL($templateUrl);
216
			$response['TemplateSource'] = $templateUrl;
217
		}
218
219
		$user = $this->userManager->get($wopi->getEditorUid());
220
		if($user !== null && $user->getAvatarImage(32) !== null) {
221
			$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
222
		}
223
224
		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...
225
			$response = $this->setFederationFileInfo($wopi, $response);
226
		}
227
228
		return new JSONResponse($response);
229
	}
230
231
	private function setFederationFileInfo($wopi, $response) {
232
		$remoteUserId = $wopi->getGuestDisplayname();
233
		$cloudID = \OC::$server->getCloudIdManager()->resolveCloudId($remoteUserId);
234
		$response['UserFriendlyName'] = $cloudID->getDisplayId();
235
		$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => 32]);
236
		$cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId());
237
		$addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']);
238
		foreach ($addressBookEntries as $entry) {
239
			if (isset($entry['CLOUD'])) {
240
				foreach ($entry['CLOUD'] as $cloudID) {
241
					if ($cloudID === $cleanCloudId) {
242
						$response['UserFriendlyName'] = $entry['FN'];
243
						break;
244
					}
245
				}
246
			}
247
		}
248
		return $response;
249
	}
250
251
	private function shouldWatermark($isPublic, $userId, $fileId, Wopi $wopi) {
252
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
253
			return false;
254
		}
255
256
		if ($isPublic) {
257
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
258
				return true;
259
			}
260 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...
261
				return true;
262
			}
263
			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...
264
				return true;
265
			}
266 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...
267
				$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
268
				$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
269
				foreach ($fileTags as $tagId) {
270
					if (in_array($tagId, $tags, true)) {
271
						return true;
272
					}
273
				}
274
			}
275
		} else {
276
			if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
277
				$files = $this->rootFolder->getUserFolder($userId)->getById($fileId);
278
				if (count($files) !== 0 && $files[0]->getOwner()->getUID() !== $userId) {
279
					return true;
280
				}
281
			}
282 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...
283
				return true;
284
			}
285
		}
286
		if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
287
			$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
288
			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...
289
				if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) {
290
					return true;
291
				}
292
			}
293
		}
294 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...
295
			$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
296
			$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
297
			foreach ($fileTags as $tagId) {
298
				if (in_array($tagId, $tags, true)) {
299
					return true;
300
				}
301
			}
302
		}
303
304
		return false;
305
	}
306
307
	/**
308
	 * Given an access token and a fileId, returns the contents of the file.
309
	 * Expects a valid token in access_token parameter.
310
	 *
311
	 * @PublicPage
312
	 * @NoCSRFRequired
313
	 *
314
	 * @param string $fileId
315
	 * @param string $access_token
316
	 * @return Http\Response
317
	 * @throws DoesNotExistException
318
	 * @throws NotFoundException
319
	 * @throws NotPermittedException
320
	 */
321
	public function getFile($fileId,
322
							$access_token) {
323
		list($fileId, , $version) = Helper::parseFileId($fileId);
324
325
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
326
327
		if ((int)$fileId !== $wopi->getFileid()) {
328
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
329
		}
330
331
		// Template is just returned as there is no version logic
332
		if ($wopi->isTemplateToken()) {
333
			$this->templateManager->setUserId($wopi->getOwnerUid());
334
			$file = $this->templateManager->get($wopi->getFileid());
335
			$response = new StreamResponse($file->fopen('rb'));
336
			$response->addHeader('Content-Disposition', 'attachment');
337
			$response->addHeader('Content-Type', 'application/octet-stream');
338
			return $response;
339
		}
340
341
		try {
342
			/** @var File $file */
343
			$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
344
			$file = $userFolder->getById($fileId)[0];
345
			\OC_User::setIncognitoMode(true);
346
			if ($version !== '0') {
347
				$view = new View('/' . $wopi->getOwnerUid() . '/files');
348
				$relPath = $view->getRelativePath($file->getPath());
349
				$versionPath = '/files_versions/' . $relPath . '.v' . $version;
350
				$view = new View('/' . $wopi->getOwnerUid());
351
				if ($view->file_exists($versionPath)){
352
					$response = new StreamResponse($view->fopen($versionPath, 'rb'));
353
				}
354
				else {
355
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
356
				}
357
			}
358
			else
359
			{
360
				$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...
361
			}
362
			$response->addHeader('Content-Disposition', 'attachment');
363
			$response->addHeader('Content-Type', 'application/octet-stream');
364
			return $response;
365
		} catch (\Exception $e) {
366
			$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...
367
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
368
		}
369
	}
370
371
	/**
372
	 * Given an access token and a fileId, replaces the files with the request body.
373
	 * Expects a valid token in access_token parameter.
374
	 *
375
	 * @PublicPage
376
	 * @NoCSRFRequired
377
	 *
378
	 * @param string $fileId
379
	 * @param string $access_token
380
	 * @return JSONResponse
381
	 * @throws DoesNotExistException
382
	 */
383
	public function putFile($fileId,
384
							$access_token) {
385
		list($fileId, ,) = Helper::parseFileId($fileId);
386
		$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
387
		$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...
388
389
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
390
		if (!$wopi->getCanwrite()) {
391
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
392
		}
393
394
		try {
395
			if ($isPutRelative) {
396
				// the new file needs to be installed in the current user dir
397
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
398
				$file = $userFolder->getById($fileId);
399
				if (count($file) === 0) {
400
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
401
				}
402
				$file = $file[0];
403
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
404
				$suggested = iconv('utf-7', 'utf-8', $suggested);
405
406 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...
407
					$path = dirname($file->getPath()) . '/New File' . $suggested;
408
				}
409
				else if ($suggested[0] !== '/') {
410
					$path = dirname($file->getPath()) . '/' . $suggested;
411
				}
412
				else {
413
					$path = $userFolder->getPath() . $suggested;
414
				}
415
416
				if ($path === '') {
417
					return new JSONResponse([
418
						'status' => 'error',
419
						'message' => 'Cannot create the file'
420
					]);
421
				}
422
423
				// create the folder first
424
				if (!$this->rootFolder->nodeExists(dirname($path))) {
425
					$this->rootFolder->newFolder(dirname($path));
426
				}
427
428
				// create a unique new file
429
				$path = $this->rootFolder->getNonExistingName($path);
430
				$this->rootFolder->newFile($path);
431
				$file = $this->rootFolder->get($path);
432
			} else {
433
				$file = $this->getFileForWopiToken($wopi);
434
				$wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp');
435
436
				if ($wopiHeaderTime !== null && $wopiHeaderTime !== Helper::toISO8601($file->getMTime() ?? 0)) {
437
					$this->logger->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [
438
						'headerTime' => $wopiHeaderTime,
439
						'storageTime' => Helper::toISO8601($file->getMTime() ?? 0)
440
					]);
441
					// Tell WOPI client about this conflict.
442
					return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT);
443
				}
444
			}
445
446
			$content = fopen('php://input', 'rb');
447
448
			// Set the user to register the change under his name
449
			$this->userScopeService->setUserScope($wopi->getEditorUid());
450
451
			try {
452
				$this->retryOperation(function () use ($file, $content){
453
					return $file->putContent($content);
454
				});
455
			} catch (LockedException $e) {
456
				$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...
457
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
458
			}
459
460
			if ($isPutRelative) {
461
				// generate a token for the new file (the user still has to be
462
				// logged in)
463
				list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
464
465
				$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
466
				$url = $this->urlGenerator->getAbsoluteURL($wopi);
467
468
				return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
469
			}
470
471
			return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMTime())]);
472
		} catch (\Exception $e) {
473
			$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...
474
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
475
		}
476
	}
477
478
	/**
479
	 * Given an access token and a fileId, replaces the files with the request body.
480
	 * Expects a valid token in access_token parameter.
481
	 * Just actually routes to the PutFile, the implementation of PutFile
482
	 * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body.
483
	 *
484
	 * @PublicPage
485
	 * @NoCSRFRequired
486
	 *
487
	 * @param string $fileId
488
	 * @param string $access_token
489
	 * @return JSONResponse
490
	 * @throws DoesNotExistException
491
	 */
492
	public function putRelativeFile($fileId,
493
					$access_token) {
494
		list($fileId, ,) = Helper::parseFileId($fileId);
495
		$wopi = $this->wopiMapper->getWopiForToken($access_token);
496
		$isRenameFile = ($this->request->getHeader('X-WOPI-Override') === 'RENAME_FILE');
497
498
		if (!$wopi->getCanwrite()) {
499
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
500
		}
501
502
		// Unless the editor is empty (public link) we modify the files as the current editor
503
		$editor = $wopi->getEditorUid();
504
		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...
505
			$editor = $wopi->getOwnerUid();
506
		}
507
508
		try {
509
			// the new file needs to be installed in the current user dir
510
			$userFolder = $this->rootFolder->getUserFolder($editor);
511
512
			if ($wopi->isTemplateToken()) {
513
				$this->templateManager->setUserId($wopi->getOwnerUid());
514
				$file = $userFolder->getById($wopi->getTemplateDestination())[0];
515
			} else if ($isRenameFile) {
516
				// the new file needs to be installed in the current user dir
517
				$userFolder = $this->rootFolder->getUserFolder($wopi->getEditorUid());
518
				$file = $userFolder->getById($fileId)[0];
519
520
				$suggested = $this->request->getHeader('X-WOPI-RequestedName');
521
522
				$suggested = iconv('utf-7', 'utf-8', $suggested) . '.' . $file->getExtension();
523
524
				if (strpos($suggested, '.') === 0) {
525
					$path = dirname($file->getPath()) . '/New File' . $suggested;
526
				}
527
				else if (strpos($suggested, '/') !== 0) {
528
					$path = dirname($file->getPath()) . '/' . $suggested;
529
				}
530
				else {
531
					$path = $userFolder->getPath() . $suggested;
532
				}
533
534
				if ($path === '') {
535
					return new JSONResponse([
536
						'status' => 'error',
537
						'message' => 'Cannot rename the file'
538
					]);
539
				}
540
541
				// create the folder first
542
				if (!$this->rootFolder->nodeExists(dirname($path))) {
543
					$this->rootFolder->newFolder(dirname($path));
544
				}
545
546
				// create a unique new file
547
				$path = $this->rootFolder->getNonExistingName($path);
548
				$file = $file->move($path);
549
			} else {
550
				$file = $userFolder->getById($fileId);
551
				if (count($file) === 0) {
552
					return new JSONResponse([], Http::STATUS_NOT_FOUND);
553
				}
554
				$file = $file[0];
555
556
				$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
557
				$suggested = iconv('utf-7', 'utf-8', $suggested);
558
559 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...
560
					$path = dirname($file->getPath()) . '/New File' . $suggested;
561
				} else if ($suggested[0] !== '/') {
562
					$path = dirname($file->getPath()) . '/' . $suggested;
563
				} else {
564
					$path = $userFolder->getPath() . $suggested;
565
				}
566
567
				if ($path === '') {
568
					return new JSONResponse([
569
						'status' => 'error',
570
						'message' => 'Cannot create the file'
571
					]);
572
				}
573
574
				// create the folder first
575
				if (!$this->rootFolder->nodeExists(dirname($path))) {
576
					$this->rootFolder->newFolder(dirname($path));
577
				}
578
579
				// create a unique new file
580
				$path = $this->rootFolder->getNonExistingName($path);
581
				$file = $this->rootFolder->newFile($path);
582
			}
583
584
			$content = fopen('php://input', 'rb');
585
586
			// Set the user to register the change under his name
587
			$this->userScopeService->setUserScope($wopi->getEditorUid());
588
589
			try {
590
				$this->retryOperation(function () use ($file, $content){
591
					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...
592
				});
593
			} catch (LockedException $e) {
594
				return new JSONResponse(['message' => 'File locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
595
			}
596
597
			// generate a token for the new file (the user still has to be
598
			// logged in)
599
			list(, $wopiToken) = $this->tokenManager->getToken($file->getId(), null, $wopi->getEditorUid());
600
601
			$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
602
			$url = $this->urlGenerator->getAbsoluteURL($wopi);
603
604
			return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
605
		} catch (\Exception $e) {
606
			$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...
607
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
608
		}
609
	}
610
611
	/**
612
	 * Retry operation if a LockedException occurred
613
	 * Other exceptions will still be thrown
614
	 * @param callable $operation
615
	 * @throws LockedException
616
	 * @throws GenericFileException
617
	 */
618
	private function retryOperation(callable $operation) {
619
		for ($i = 0; $i < 5; $i++) {
620
			try {
621
				if ($operation() !== false) {
622
					return;
623
				}
624
			} catch (LockedException $e) {
625
				if ($i === 4) {
626
					throw $e;
627
				}
628
				usleep(500000);
629
			}
630
		}
631
		throw new GenericFileException('Operation failed after multiple retries');
632
	}
633
634
	/**
635
	 * @param Wopi $wopi
636
	 * @return File|Folder|Node|null
637
	 * @throws NotFoundException
638
	 * @throws ShareNotFound
639
	 */
640
	private function getFileForWopiToken(Wopi $wopi) {
641
		$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...
642
643
		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...
644
			$share = $this->shareManager->getShareByToken($wopi->getEditorUid());
645
			$node = $share->getNode();
646
			if ($node instanceof Folder) {
647
				$file = $node->getById($wopi->getFileid())[0];
648
			} else {
649
				$file = $node;
650
			}
651
		} else {
652
			// Unless the editor is empty (public link) we modify the files as the current editor
653
			// TODO: add related share token to the wopi table so we can obtain the
654
			$editor = $wopi->getEditorUid();
655
656
			// Use the actual file owner no editor is available
657
			if ($editor === null || $wopi->getGuestDisplayname() === null) {
658
				$editor = $wopi->getOwnerUid();
659
			}
660
661
			$userFolder = $this->rootFolder->getUserFolder($editor);
662
			$files = $userFolder->getById($wopi->getFileid());
663
			if (isset($files[0]) && $files[0] instanceof File) {
664
				$file = $files[0];
665
			} else {
666
				throw new NotFoundException('No valid file found for wopi token');
667
			}
668
		}
669
		return $file;
670
	}
671
672
	/**
673
	 * Endpoint to return the template file that is requested by collabora to create a new document
674
	 *
675
	 * @PublicPage
676
	 * @NoCSRFRequired
677
	 *
678
	 * @param $fileId
679
	 * @param $access_token
680
	 * @return JSONResponse|StreamResponse
681
	 */
682
	public function getTemplate($fileId, $access_token) {
683
		try {
684
			$wopi = $this->wopiMapper->getPathForToken($access_token);
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Richdocuments\Db\WopiMapper::getPathForToken() has been deprecated.

This method has been deprecated.

Loading history...
685
		} catch (DoesNotExistException $e) {
686
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
687
		}
688
689
		if ((int)$fileId !== $wopi->getTemplateId()) {
690
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
691
		}
692
693
		try {
694
			$this->templateManager->setUserId($wopi->getOwnerUid());
695
			$file = $this->templateManager->get($wopi->getTemplateId());
696
			$response = new StreamResponse($file->fopen('rb'));
697
			$response->addHeader('Content-Disposition', 'attachment');
698
			$response->addHeader('Content-Type', 'application/octet-stream');
699
			return $response;
700
		} catch (\Exception $e) {
701
			$this->logger->logException($e, ['level' => ILogger::ERROR,	'app' => 'richdocuments', 'message' => 'getTemplate 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...
702
			return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
703
		}
704
	}
705
706
}
707