Completed
Push — master ( 859d31...2a29cb )
by Julius
03:47 queued 11s
created

WopiController::getFileForWopiToken()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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