Completed
Push — master ( 76ba1c...91e148 )
by Thomas
23:00 queued 09:19
created

FilesPlugin::beforeMoveFutureFile()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 3
nop 2
dl 0
loc 26
rs 8.8571
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 Michael Jobst <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2017, ownCloud GmbH
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\DAV\Connector\Sabre;
32
33
use OC\AppFramework\Http\Request;
34
use OCP\Files\ForbiddenException;
35
use Sabre\DAV\Exception\Forbidden;
36
use Sabre\DAV\Exception\NotFound;
37
use Sabre\DAV\IFile;
38
use \Sabre\DAV\PropFind;
39
use \Sabre\DAV\PropPatch;
40
use Sabre\DAV\ServerPlugin;
41
use Sabre\DAV\Tree;
42
use \Sabre\HTTP\RequestInterface;
43
use \Sabre\HTTP\ResponseInterface;
44
use OCP\Files\StorageNotAvailableException;
45
use OCP\IConfig;
46
use OCP\IRequest;
47
48
class FilesPlugin extends ServerPlugin {
49
50
	// namespace
51
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
52
	const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
53
	const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
54
	const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
55
	const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
56
	const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
57
	const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
58
	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
59
	const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
60
	const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
61
	const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
62
	const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
63
	const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
64
65
	/**
66
	 * Reference to main server object
67
	 *
68
	 * @var \Sabre\DAV\Server
69
	 */
70
	private $server;
71
72
	/**
73
	 * @var Tree
74
	 */
75
	private $tree;
76
77
	/**
78
	 * Whether this is public webdav.
79
	 * If true, some returned information will be stripped off.
80
	 *
81
	 * @var bool
82
	 */
83
	private $isPublic;
84
85
	/**
86
	 * @var bool
87
	 */
88
	private $downloadAttachment;
89
90
	/**
91
	 * @var IConfig
92
	 */
93
	private $config;
94
95
	/**
96
	 * @var IRequest
97
	 */
98
	private $request;
99
100
	/**
101
	 * @param Tree $tree
102
	 * @param IConfig $config
103
	 * @param IRequest $request
104
	 * @param bool $isPublic
105
	 * @param bool $downloadAttachment
106
	 */
107
	public function __construct(Tree $tree,
108
								IConfig $config,
109
								IRequest $request,
110
								$isPublic = false,
111
								$downloadAttachment = true) {
112
		$this->tree = $tree;
113
		$this->config = $config;
114
		$this->request = $request;
115
		$this->isPublic = $isPublic;
116
		$this->downloadAttachment = $downloadAttachment;
117
	}
118
119
	/**
120
	 * This initializes the plugin.
121
	 *
122
	 * This function is called by \Sabre\DAV\Server, after
123
	 * addPlugin is called.
124
	 *
125
	 * This method should set up the required event subscriptions.
126
	 *
127
	 * @param \Sabre\DAV\Server $server
128
	 * @return void
129
	 */
130
	public function initialize(\Sabre\DAV\Server $server) {
131
132
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
133
		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
134
		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
135
		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
136
		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
137
		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
138
		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
139
		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
140
		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
141
		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
142
		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
143
144
		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
145
		$allowedProperties = ['{DAV:}getetag'];
146
		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
147
148
		$this->server = $server;
149
		$this->server->on('propFind', [$this, 'handleGetProperties']);
150
		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
151
		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
152
		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
153
		$this->server->on('afterMethod:GET', [$this,'httpGet']);
154
		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
155
		$this->server->on('afterResponse', function($request, ResponseInterface $response) {
156
			$body = $response->getBody();
157
			if (is_resource($body)) {
158
				fclose($body);
159
			}
160
		});
161
		$this->server->on('beforeMove', [$this, 'checkMove']);
162
	}
163
164
	/**
165
	 * Plugin that checks if a move can actually be performed.
166
	 *
167
	 * @param string $source source path
168
	 * @param string $destination destination path
169
	 * @throws Forbidden
170
	 * @throws NotFound
171
	 */
172
	function checkMove($source, $destination) {
173
		$sourceNode = $this->tree->getNodeForPath($source);
174
		if (!$sourceNode instanceof Node) {
175
			return;
176
		}
177
		list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source);
178
		list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
179
180
		if ($sourceDir !== $destinationDir) {
181
			$sourceNodeFileInfo = $sourceNode->getFileInfo();
182
			if (is_null($sourceNodeFileInfo)) {
183
				throw new NotFound($source . ' does not exist');
184
			}
185
186
			if (!$sourceNodeFileInfo->isDeletable()) {
187
				throw new Forbidden($source . " cannot be deleted");
188
			}
189
		}
190
	}
191
192
	/**
193
	 * This sets a cookie to be able to recognize the start of the download
194
	 * the content must not be longer than 32 characters and must only contain
195
	 * alphanumeric characters
196
	 *
197
	 * @param RequestInterface $request
198
	 * @param ResponseInterface $response
199
	 */
200
	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...
201
		$queryParams = $request->getQueryParameters();
202
203
		/**
204
		 * this sets a cookie to be able to recognize the start of the download
205
		 * the content must not be longer than 32 characters and must only contain
206
		 * alphanumeric characters
207
		 */
208
		if (isset($queryParams['downloadStartSecret'])) {
209
			$token = $queryParams['downloadStartSecret'];
210 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...
211
				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
212
				// FIXME: use $response->setHeader() instead
213
				setcookie('ocDownloadStarted', $token, time() + 20, '/');
214
			}
215
		}
216
	}
217
218
	/**
219
	 * Add headers to file download
220
	 *
221
	 * @param RequestInterface $request
222
	 * @param ResponseInterface $response
223
	 */
224
	function httpGet(RequestInterface $request, ResponseInterface $response) {
225
		// Only handle valid files
226
		$node = $this->tree->getNodeForPath($request->getPath());
227
		if (!($node instanceof IFile)) return;
228
229
		// adds a 'Content-Disposition: attachment' header
230
		if ($this->downloadAttachment) {
231
			$filename = $node->getName();
232
			if ($this->request->isUserAgent(
233
				[
234
					Request::USER_AGENT_IE,
235
					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
236
					Request::USER_AGENT_FREEBOX,
237
				])) {
238
				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
239
			} else {
240
				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
241
													 . '; filename="' . rawurlencode($filename) . '"');
242
			}
243
		}
244
245
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
246
			//Add OC-Checksum header
247
			/** @var $node File */
248
			$checksum = $node->getChecksum('sha1');
249
			if ($checksum !== null && $checksum !== '') {
250
				$response->addHeader('OC-Checksum', $checksum);
251
			}
252
		}
253
	}
254
255
	/**
256
	 * Adds all ownCloud-specific properties
257
	 *
258
	 * @param PropFind $propFind
259
	 * @param \Sabre\DAV\INode $node
260
	 * @return void
261
	 */
262
	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
263
264
		$httpRequest = $this->server->httpRequest;
265
266
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
267
			if (!$node->getFileInfo()->isReadable()) {
268
				// avoid detecting files through this means
269
				throw new NotFound();
270
			}
271
272
			$propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) {
273
				return $node->getFileId();
274
			});
275
276
			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
277
				return $node->getInternalFileId();
278
			});
279
280
			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
281
				$perms = $node->getDavPermissions();
282
				if ($this->isPublic) {
283
					// remove mount information
284
					$perms = str_replace(['S', 'M'], '', $perms);
285
				}
286
				return $perms;
287
			});
288
289
			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
290
				return $node->getSharePermissions(
291
					$httpRequest->getRawServerValue('PHP_AUTH_USER')
292
				);
293
			});
294
295
			$propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) {
296
				return $node->getETag();
297
			});
298
299
			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
300
				$owner = $node->getOwner();
301
				return $owner->getUID();
302
			});
303
			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
304
				$owner = $node->getOwner();
305
				$displayName = $owner->getDisplayName();
306
				return $displayName;
307
			});
308
			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
309
				return $node->getSize();
310
			});
311
		}
312
313
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
314
			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
315
				return $this->config->getSystemValue('data-fingerprint', '');
316
			});
317
		}
318
319
		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
320
			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) {
321
				/** @var $node \OCA\DAV\Connector\Sabre\File */
322
				try {
323
					$directDownloadUrl = $node->getDirectDownload();
324
					if (isset($directDownloadUrl['url'])) {
325
						return $directDownloadUrl['url'];
326
					}
327
				} catch (StorageNotAvailableException $e) {
328
					return false;
329
				} catch (ForbiddenException $e) {
330
					return false;
331
				}
332
				return false;
333
			});
334
335
			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
336
				$checksum = $node->getChecksum();
337
				if ($checksum === NULL || $checksum === '') {
338
					return null;
339
				}
340
341
				return new ChecksumList($checksum);
342
			});
343
344
		}
345
346
		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
347
			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
348
				return $node->getSize();
349
			});
350
		}
351
	}
352
353
	/**
354
	 * Update ownCloud-specific properties
355
	 *
356
	 * @param string $path
357
	 * @param PropPatch $propPatch
358
	 *
359
	 * @return void
360
	 */
361
	public function handleUpdateProperties($path, PropPatch $propPatch) {
362
		$node = $this->tree->getNodeForPath($path);
363
		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
364
			return;
365
		}
366
367
		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) {
368
			if (empty($time)) {
369
				return false;
370
			}
371
			$node->touch($time);
372
			return true;
373
		});
374
		$propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) {
375
			if (empty($etag)) {
376
				return false;
377
			}
378
			if ($node->setEtag($etag) !== -1) {
379
				return true;
380
			}
381
			return false;
382
		});
383
	}
384
385
	/**
386
	 * @param string $filePath
387
	 * @param \Sabre\DAV\INode $node
388
	 * @throws \Sabre\DAV\Exception\BadRequest
389
	 */
390
	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...
391
		// chunked upload handling
392
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
393
			list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath);
394
			$info = \OC_FileChunking::decodeName($name);
395
			if (!empty($info)) {
396
				$filePath = $path . '/' . $info['name'];
397
			}
398
		}
399
400
		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
401
		if (!$this->server->tree->nodeExists($filePath)) {
402
			return;
403
		}
404
		$node = $this->server->tree->getNodeForPath($filePath);
405
		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
406
			$fileId = $node->getFileId();
407
			if (!is_null($fileId)) {
408
				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
409
			}
410
		}
411
	}
412
}
413