Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
created

FilesPlugin::handleUpdateProperties()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 26
Code Lines 18

Duplication

Lines 24
Ratio 92.31 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 1
nop 2
dl 24
loc 26
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Lukas Reschke <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 * @author Vincent Petry <[email protected]>
12
 *
13
 * @copyright Copyright (c) 2016, ownCloud GmbH.
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\DAV\Connector\Sabre;
31
32
use OC\Files\View;
33
use OCP\Files\ForbiddenException;
34
use Sabre\DAV\Exception\Forbidden;
35
use Sabre\DAV\Exception\NotFound;
36
use Sabre\DAV\IFile;
37
use \Sabre\DAV\PropFind;
38
use \Sabre\DAV\PropPatch;
39
use Sabre\DAV\ServerPlugin;
40
use Sabre\DAV\Tree;
41
use \Sabre\HTTP\RequestInterface;
42
use \Sabre\HTTP\ResponseInterface;
43
use OCP\Files\StorageNotAvailableException;
44
use OCP\IConfig;
45
use OCP\IRequest;
46
47
class FilesPlugin extends ServerPlugin {
48
49
	// namespace
50
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
51
	const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
52
	const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
53
	const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
54
	const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
55
	const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
56
	const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
57
	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
58
	const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
59
	const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
60
	const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
61
	const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
62
	const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
63
64
	/**
65
	 * Reference to main server object
66
	 *
67
	 * @var \Sabre\DAV\Server
68
	 */
69
	private $server;
70
71
	/**
72
	 * @var Tree
73
	 */
74
	private $tree;
75
76
	/**
77
	 * Whether this is public webdav.
78
	 * If true, some returned information will be stripped off.
79
	 *
80
	 * @var bool
81
	 */
82
	private $isPublic;
83
84
	/**
85
	 * @var View
86
	 */
87
	private $fileView;
88
89
	/**
90
	 * @var bool
91
	 */
92
	private $downloadAttachment;
93
94
	/**
95
	 * @var IConfig
96
	 */
97
	private $config;
98
99
	/**
100
	 * @var IRequest
101
	 */
102
	private $request;
103
104
	/**
105
	 * @param Tree $tree
106
	 * @param View $view
107
	 * @param IConfig $config
108
	 * @param IRequest $request
109
	 * @param bool $isPublic
110
	 * @param bool $downloadAttachment
111
	 */
112
	public function __construct(Tree $tree,
113
								View $view,
114
								IConfig $config,
115
								IRequest $request,
116
								$isPublic = false,
117
								$downloadAttachment = true) {
118
		$this->tree = $tree;
119
		$this->fileView = $view;
120
		$this->config = $config;
121
		$this->request = $request;
122
		$this->isPublic = $isPublic;
123
		$this->downloadAttachment = $downloadAttachment;
124
	}
125
126
	/**
127
	 * This initializes the plugin.
128
	 *
129
	 * This function is called by \Sabre\DAV\Server, after
130
	 * addPlugin is called.
131
	 *
132
	 * This method should set up the required event subscriptions.
133
	 *
134
	 * @param \Sabre\DAV\Server $server
135
	 * @return void
136
	 */
137
	public function initialize(\Sabre\DAV\Server $server) {
138
139
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
140
		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
141
		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
142
		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
143
		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
144
		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
145
		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
146
		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
147
		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
148
		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
149
		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
150
151
		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
152
		$allowedProperties = ['{DAV:}getetag'];
153
		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
154
155
		$this->server = $server;
156
		$this->server->on('propFind', array($this, 'handleGetProperties'));
157
		$this->server->on('propPatch', array($this, 'handleUpdateProperties'));
158
		$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
159
		$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
160
		$this->server->on('afterMethod:GET', [$this,'httpGet']);
161
		$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
162
		$this->server->on('afterResponse', function($request, ResponseInterface $response) {
163
			$body = $response->getBody();
164
			if (is_resource($body)) {
165
				fclose($body);
166
			}
167
		});
168
		$this->server->on('beforeMove', [$this, 'checkMove']);
169
	}
170
171
	/**
172
	 * Plugin that checks if a move can actually be performed.
173
	 *
174
	 * @param string $source source path
175
	 * @param string $destination destination path
176
	 * @throws Forbidden
177
	 * @throws NotFound
178
	 */
179
	function checkMove($source, $destination) {
180
		$sourceNode = $this->tree->getNodeForPath($source);
181
		if (!$sourceNode instanceof Node) {
182
			return;
183
		}
184
		list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source);
185
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
186
187
		if ($sourceDir !== $destinationDir) {
188
			$sourceNodeFileInfo = $sourceNode->getFileInfo();
189
			if (is_null($sourceNodeFileInfo)) {
190
				throw new NotFound($source . ' does not exist');
191
			}
192
193
			if (!$sourceNodeFileInfo->isDeletable()) {
194
				throw new Forbidden($source . " cannot be deleted");
195
			}
196
		}
197
	}
198
199
	/**
200
	 * This sets a cookie to be able to recognize the start of the download
201
	 * the content must not be longer than 32 characters and must only contain
202
	 * alphanumeric characters
203
	 *
204
	 * @param RequestInterface $request
205
	 * @param ResponseInterface $response
206
	 */
207
	function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
208
		$queryParams = $request->getQueryParameters();
209
210
		/**
211
		 * this sets a cookie to be able to recognize the start of the download
212
		 * the content must not be longer than 32 characters and must only contain
213
		 * alphanumeric characters
214
		 */
215
		if (isset($queryParams['downloadStartSecret'])) {
216
			$token = $queryParams['downloadStartSecret'];
217 View Code Duplication
			if (!isset($token[32])
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...
218
				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
219
				// FIXME: use $response->setHeader() instead
220
				setcookie('ocDownloadStarted', $token, time() + 20, '/');
221
			}
222
		}
223
	}
224
225
	/**
226
	 * Add headers to file download
227
	 *
228
	 * @param RequestInterface $request
229
	 * @param ResponseInterface $response
230
	 */
231
	function httpGet(RequestInterface $request, ResponseInterface $response) {
232
		// Only handle valid files
233
		$node = $this->tree->getNodeForPath($request->getPath());
234
		if (!($node instanceof IFile)) return;
235
236
		// adds a 'Content-Disposition: attachment' header
237
		if ($this->downloadAttachment) {
238
			$filename = $node->getName();
239
			if ($this->request->isUserAgent(
240
				[
241
					\OC\AppFramework\Http\Request::USER_AGENT_IE,
242
					\OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME,
243
					\OC\AppFramework\Http\Request::USER_AGENT_FREEBOX,
244
				])) {
245
				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
246
			} else {
247
				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
248
													 . '; filename="' . rawurlencode($filename) . '"');
249
			}
250
		}
251
252
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
253
			//Add OC-Checksum header
254
			/** @var $node File */
255
			$checksum = $node->getChecksum();
256
			if ($checksum !== null && $checksum !== '') {
257
				$response->addHeader('OC-Checksum', $checksum);
258
			}
259
		}
260
	}
261
262
	/**
263
	 * Adds all ownCloud-specific properties
264
	 *
265
	 * @param PropFind $propFind
266
	 * @param \Sabre\DAV\INode $node
267
	 * @return void
268
	 */
269
	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
270
271
		$httpRequest = $this->server->httpRequest;
272
273
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
274
275
			$propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) {
276
				return $node->getFileId();
277
			});
278
279
			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
280
				return $node->getInternalFileId();
281
			});
282
283
			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
284
				$perms = $node->getDavPermissions();
285
				if ($this->isPublic) {
286
					// remove mount information
287
					$perms = str_replace(['S', 'M'], '', $perms);
288
				}
289
				return $perms;
290
			});
291
292
			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
293
				return $node->getSharePermissions(
294
					$httpRequest->getRawServerValue('PHP_AUTH_USER')
295
				);
296
			});
297
298
			$propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) {
299
				return $node->getETag();
300
			});
301
302
			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
303
				$owner = $node->getOwner();
304
				return $owner->getUID();
305
			});
306
			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
307
				$owner = $node->getOwner();
308
				$displayName = $owner->getDisplayName();
309
				return $displayName;
310
			});
311
		}
312
313
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node
314
			|| $node instanceof \OCA\DAV\Files\FilesHome) {
315
			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
316
				return $this->config->getSystemValue('data-fingerprint', '');
317
			});
318
		}
319
320
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
321
			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) {
322
				/** @var $node \OCA\DAV\Connector\Sabre\File */
323
				try {
324
					$directDownloadUrl = $node->getDirectDownload();
325
					if (isset($directDownloadUrl['url'])) {
326
						return $directDownloadUrl['url'];
327
					}
328
				} catch (StorageNotAvailableException $e) {
329
					return false;
330
				} catch (ForbiddenException $e) {
331
					return false;
332
				}
333
				return false;
334
			});
335
336
			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
337
				$checksum = $node->getChecksum();
338
				if ($checksum === NULL || $checksum === '') {
339
					return null;
340
				}
341
342
				return new ChecksumList($checksum);
343
			});
344
345
		}
346
347
		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
348
			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
349
				return $node->getSize();
350
			});
351
		}
352
	}
353
354
	/**
355
	 * Update ownCloud-specific properties
356
	 *
357
	 * @param string $path
358
	 * @param PropPatch $propPatch
359
	 *
360
	 * @return void
361
	 */
362
	public function handleUpdateProperties($path, PropPatch $propPatch) {
363 View Code Duplication
		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($path) {
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...
364
			if (empty($time)) {
365
				return false;
366
			}
367
			$node = $this->tree->getNodeForPath($path);
368
			if (is_null($node)) {
369
				return 404;
370
			}
371
			$node->touch($time);
372
			return true;
373
		});
374 View Code Duplication
		$propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($path) {
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...
375
			if (empty($etag)) {
376
				return false;
377
			}
378
			$node = $this->tree->getNodeForPath($path);
379
			if (is_null($node)) {
380
				return 404;
381
			}
382
			if ($node->setEtag($etag) !== -1) {
383
				return true;
384
			}
385
			return false;
386
		});
387
	}
388
389
	/**
390
	 * @param string $filePath
391
	 * @param \Sabre\DAV\INode $node
392
	 * @throws \Sabre\DAV\Exception\BadRequest
393
	 */
394
	public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
395
		// chunked upload handling
396
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
397
			list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath);
398
			$info = \OC_FileChunking::decodeName($name);
399
			if (!empty($info)) {
400
				$filePath = $path . '/' . $info['name'];
401
			}
402
		}
403
404
		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
405
		if (!$this->server->tree->nodeExists($filePath)) {
406
			return;
407
		}
408
		$node = $this->server->tree->getNodeForPath($filePath);
409
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
410
			$fileId = $node->getFileId();
411
			if (!is_null($fileId)) {
412
				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
413
			}
414
		}
415
	}
416
417
}
418