Passed
Push — master ( 2be893...8e65f0 )
by Morris
29:15 queued 17:51
created

File::emitPreHooks()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 4
nop 2
dl 0
loc 23
rs 9.7333
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Jakob Sack <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Owen Winkler <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Semih Serhat Karakaya <[email protected]>
16
 * @author Stefan Schneider <[email protected]>
17
 * @author Thomas Müller <[email protected]>
18
 * @author Vincent Petry <[email protected]>
19
 * @author Vinicius Cubas Brand <[email protected]>
20
 *
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
37
namespace OCA\DAV\Connector\Sabre;
38
39
use OC\AppFramework\Http\Request;
40
use OC\Files\Filesystem;
41
use OC\Files\View;
42
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
43
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
44
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
45
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType;
46
use OCP\Encryption\Exceptions\GenericEncryptionException;
47
use OCP\Files\EntityTooLargeException;
48
use OCP\Files\FileInfo;
49
use OCP\Files\ForbiddenException;
50
use OCP\Files\InvalidContentException;
51
use OCP\Files\InvalidPathException;
52
use OCP\Files\LockNotAcquiredException;
53
use OCP\Files\NotFoundException;
54
use OCP\Files\NotPermittedException;
55
use OCP\Files\Storage;
56
use OCP\Files\StorageNotAvailableException;
57
use OCP\Lock\ILockingProvider;
58
use OCP\Lock\LockedException;
59
use OCP\Share\IManager;
60
use Sabre\DAV\Exception;
61
use Sabre\DAV\Exception\BadRequest;
62
use Sabre\DAV\Exception\Forbidden;
63
use Sabre\DAV\Exception\NotImplemented;
64
use Sabre\DAV\Exception\ServiceUnavailable;
65
use Sabre\DAV\IFile;
66
use Sabre\DAV\Exception\NotFound;
67
68
class File extends Node implements IFile {
69
70
	protected $request;
71
72
	/**
73
	 * Sets up the node, expects a full path name
74
	 *
75
	 * @param \OC\Files\View $view
76
	 * @param \OCP\Files\FileInfo $info
77
	 * @param \OCP\Share\IManager $shareManager
78
	 * @param \OC\AppFramework\Http\Request $request
79
	 */
80
	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
81
		parent::__construct($view, $info, $shareManager);
82
83
		if (isset($request)) {
84
			$this->request = $request;
85
		} else {
86
			$this->request = \OC::$server->getRequest();
87
		}
88
	}
89
90
	/**
91
	 * Updates the data
92
	 *
93
	 * The data argument is a readable stream resource.
94
	 *
95
	 * After a successful put operation, you may choose to return an ETag. The
96
	 * etag must always be surrounded by double-quotes. These quotes must
97
	 * appear in the actual string you're returning.
98
	 *
99
	 * Clients may use the ETag from a PUT request to later on make sure that
100
	 * when they update the file, the contents haven't changed in the mean
101
	 * time.
102
	 *
103
	 * If you don't plan to store the file byte-by-byte, and you return a
104
	 * different object on a subsequent GET you are strongly recommended to not
105
	 * return an ETag, and just return null.
106
	 *
107
	 * @param resource $data
108
	 *
109
	 * @throws Forbidden
110
	 * @throws UnsupportedMediaType
111
	 * @throws BadRequest
112
	 * @throws Exception
113
	 * @throws EntityTooLarge
114
	 * @throws ServiceUnavailable
115
	 * @throws FileLocked
116
	 * @return string|null
117
	 */
118
	public function put($data) {
119
		try {
120
			$exists = $this->fileView->file_exists($this->path);
121
			if ($this->info && $exists && !$this->info->isUpdateable()) {
122
				throw new Forbidden();
123
			}
124
		} catch (StorageNotAvailableException $e) {
125
			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
126
		}
127
128
		// verify path of the target
129
		$this->verifyPath();
130
131
		// chunked handling
132
		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
133
			try {
134
				return $this->createFileChunked($data);
135
			} catch (\Exception $e) {
136
				$this->convertToSabreException($e);
137
			}
138
		}
139
140
		/** @var Storage $partStorage */
141
		list($partStorage) = $this->fileView->resolvePath($this->path);
142
		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
143
144
		$view = \OC\Files\Filesystem::getView();
145
146
		if ($needsPartFile) {
147
			// mark file as partial while uploading (ignored by the scanner)
148
			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
149
		} else {
150
			// upload file directly as the final path
151
			$partFilePath = $this->path;
152
153
			if ($view && !$this->emitPreHooks($exists)) {
154
				throw new Exception('Could not write to final file, canceled by hook');
155
			}
156
		}
157
158
		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
159
		/** @var \OC\Files\Storage\Storage $partStorage */
160
		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
161
		/** @var \OC\Files\Storage\Storage $storage */
162
		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
163
		try {
164
			if (!$needsPartFile) {
165
				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
166
			}
167
168
			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
169
				$count = $partStorage->writeStream($internalPartPath, $data);
0 ignored issues
show
Bug introduced by
The method writeStream() does not exist on OC\Files\Storage\Storage. Since it exists in all sub-types, consider adding an abstract or default implementation to OC\Files\Storage\Storage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

169
				/** @scrutinizer ignore-call */ 
170
    $count = $partStorage->writeStream($internalPartPath, $data);
Loading history...
170
				$result = $count > 0;
171
				if ($result === false) {
172
					$result = feof($data);
173
				}
174
175
			} else {
176
				$target = $partStorage->fopen($internalPartPath, 'wb');
177
				if ($target === false) {
178
					\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
179
					// because we have no clue about the cause we can only throw back a 500/Internal Server Error
180
					throw new Exception('Could not write file contents');
181
				}
182
				list($count, $result) = \OC_Helper::streamCopy($data, $target);
183
				fclose($target);
184
			}
185
186
			if ($result === false) {
187
				$expected = -1;
188
				if (isset($_SERVER['CONTENT_LENGTH'])) {
189
					$expected = $_SERVER['CONTENT_LENGTH'];
190
				}
191
				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
192
			}
193
194
			// if content length is sent by client:
195
			// double check if the file was fully received
196
			// compare expected and actual size
197
			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
198
				$expected = (int)$_SERVER['CONTENT_LENGTH'];
199
				if ($count !== $expected) {
200
					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
201
				}
202
			}
203
204
		} catch (\Exception $e) {
205
			\OC::$server->getLogger()->logException($e);
206
			if ($needsPartFile) {
207
				$partStorage->unlink($internalPartPath);
208
			}
209
			$this->convertToSabreException($e);
210
		}
211
212
		try {
213
			if ($needsPartFile) {
214
				if ($view && !$this->emitPreHooks($exists)) {
215
					$partStorage->unlink($internalPartPath);
216
					throw new Exception('Could not rename part file to final file, canceled by hook');
217
				}
218
				try {
219
					$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
220
				} catch (LockedException $e) {
221
					if ($needsPartFile) {
0 ignored issues
show
introduced by
The condition $needsPartFile is always true.
Loading history...
222
						$partStorage->unlink($internalPartPath);
223
					}
224
					throw new FileLocked($e->getMessage(), $e->getCode(), $e);
225
				}
226
227
				// rename to correct path
228
				try {
229
					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
230
					$fileExists = $storage->file_exists($internalPath);
231
					if ($renameOkay === false || $fileExists === false) {
232
						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
233
						throw new Exception('Could not rename part file to final file');
234
					}
235
				} catch (ForbiddenException $ex) {
236
					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
237
				} catch (\Exception $e) {
238
					$partStorage->unlink($internalPartPath);
239
					$this->convertToSabreException($e);
240
				}
241
			}
242
243
			// since we skipped the view we need to scan and emit the hooks ourselves
244
			$storage->getUpdater()->update($internalPath);
245
246
			try {
247
				$this->changeLock(ILockingProvider::LOCK_SHARED);
248
			} catch (LockedException $e) {
249
				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
250
			}
251
252
			// allow sync clients to send the mtime along in a header
253
			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
254
				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
255
				if ($this->fileView->touch($this->path, $mtime)) {
256
					$this->header('X-OC-MTime: accepted');
257
				}
258
			}
259
260
			if ($view) {
0 ignored issues
show
introduced by
$view is of type OC\Files\View, thus it always evaluated to true.
Loading history...
261
				$this->emitPostHooks($exists);
262
			}
263
264
			$this->refreshInfo();
265
266
			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
267
				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
268
				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
269
				$this->refreshInfo();
270
			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
271
				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
272
				$this->refreshInfo();
273
			}
274
275
		} catch (StorageNotAvailableException $e) {
276
			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
277
		}
278
279
		return '"' . $this->info->getEtag() . '"';
280
	}
281
282
	private function getPartFileBasePath($path) {
283
		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
284
		if ($partFileInStorage) {
285
			return $path;
286
		} else {
287
			return md5($path); // will place it in the root of the view with a unique name
288
		}
289
	}
290
291
	/**
292
	 * @param string $path
293
	 */
294
	private function emitPreHooks($exists, $path = null) {
295
		if (is_null($path)) {
296
			$path = $this->path;
297
		}
298
		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
299
		$run = true;
300
301
		if (!$exists) {
302
			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
303
				\OC\Files\Filesystem::signal_param_path => $hookPath,
304
				\OC\Files\Filesystem::signal_param_run => &$run,
305
			));
306
		} else {
307
			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
308
				\OC\Files\Filesystem::signal_param_path => $hookPath,
309
				\OC\Files\Filesystem::signal_param_run => &$run,
310
			));
311
		}
312
		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
313
			\OC\Files\Filesystem::signal_param_path => $hookPath,
314
			\OC\Files\Filesystem::signal_param_run => &$run,
315
		));
316
		return $run;
317
	}
318
319
	/**
320
	 * @param string $path
321
	 */
322
	private function emitPostHooks($exists, $path = null) {
323
		if (is_null($path)) {
324
			$path = $this->path;
325
		}
326
		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
327
		if (!$exists) {
328
			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
329
				\OC\Files\Filesystem::signal_param_path => $hookPath
330
			));
331
		} else {
332
			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
333
				\OC\Files\Filesystem::signal_param_path => $hookPath
334
			));
335
		}
336
		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
337
			\OC\Files\Filesystem::signal_param_path => $hookPath
338
		));
339
	}
340
341
	/**
342
	 * Returns the data
343
	 *
344
	 * @return resource
345
	 * @throws Forbidden
346
	 * @throws ServiceUnavailable
347
	 */
348
	public function get() {
349
		//throw exception if encryption is disabled but files are still encrypted
350
		try {
351
			if (!$this->info->isReadable()) {
352
				// do a if the file did not exist
353
				throw new NotFound();
354
			}
355
			try {
356
				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
357
			} catch (\Exception $e) {
358
				$this->convertToSabreException($e);
359
			}
360
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
361
				throw new ServiceUnavailable("Could not open file");
362
			}
363
			return $res;
364
		} catch (GenericEncryptionException $e) {
365
			// returning 503 will allow retry of the operation at a later point in time
366
			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
367
		} catch (StorageNotAvailableException $e) {
368
			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
369
		} catch (ForbiddenException $ex) {
370
			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
371
		} catch (LockedException $e) {
372
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
373
		}
374
	}
375
376
	/**
377
	 * Delete the current file
378
	 *
379
	 * @throws Forbidden
380
	 * @throws ServiceUnavailable
381
	 */
382
	public function delete() {
383
		if (!$this->info->isDeletable()) {
384
			throw new Forbidden();
385
		}
386
387
		try {
388
			if (!$this->fileView->unlink($this->path)) {
389
				// assume it wasn't possible to delete due to permissions
390
				throw new Forbidden();
391
			}
392
		} catch (StorageNotAvailableException $e) {
393
			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
394
		} catch (ForbiddenException $ex) {
395
			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
396
		} catch (LockedException $e) {
397
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
398
		}
399
	}
400
401
	/**
402
	 * Returns the mime-type for a file
403
	 *
404
	 * If null is returned, we'll assume application/octet-stream
405
	 *
406
	 * @return string
407
	 */
408
	public function getContentType() {
409
		$mimeType = $this->info->getMimetype();
410
411
		// PROPFIND needs to return the correct mime type, for consistency with the web UI
412
		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
413
			return $mimeType;
414
		}
415
		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
416
	}
417
418
	/**
419
	 * @return array|false
420
	 */
421
	public function getDirectDownload() {
422
		if (\OCP\App::isEnabled('encryption')) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\App::isEnabled() has been deprecated: 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

422
		if (/** @scrutinizer ignore-deprecated */ \OCP\App::isEnabled('encryption')) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
423
			return [];
424
		}
425
		/** @var \OCP\Files\Storage $storage */
426
		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
427
		if (is_null($storage)) {
428
			return [];
429
		}
430
431
		return $storage->getDirectDownload($internalPath);
432
	}
433
434
	/**
435
	 * @param resource $data
436
	 * @return null|string
437
	 * @throws Exception
438
	 * @throws BadRequest
439
	 * @throws NotImplemented
440
	 * @throws ServiceUnavailable
441
	 */
442
	private function createFileChunked($data) {
443
		list($path, $name) = \Sabre\Uri\split($this->path);
444
445
		$info = \OC_FileChunking::decodeName($name);
446
		if (empty($info)) {
447
			throw new NotImplemented('Invalid chunk name');
448
		}
449
450
		$chunk_handler = new \OC_FileChunking($info);
451
		$bytesWritten = $chunk_handler->store($info['index'], $data);
452
453
		//detect aborted upload
454
		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
455
			if (isset($_SERVER['CONTENT_LENGTH'])) {
456
				$expected = (int)$_SERVER['CONTENT_LENGTH'];
457
				if ($bytesWritten !== $expected) {
458
					$chunk_handler->remove($info['index']);
459
					throw new BadRequest(
460
						'expected filesize ' . $expected . ' got ' . $bytesWritten);
461
				}
462
			}
463
		}
464
465
		if ($chunk_handler->isComplete()) {
466
			/** @var Storage $storage */
467
			list($storage,) = $this->fileView->resolvePath($path);
468
			$needsPartFile = $storage->needsPartFile();
469
			$partFile = null;
470
471
			$targetPath = $path . '/' . $info['name'];
472
			/** @var \OC\Files\Storage\Storage $targetStorage */
473
			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
474
475
			$exists = $this->fileView->file_exists($targetPath);
476
477
			try {
478
				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
479
480
				$this->emitPreHooks($exists, $targetPath);
481
				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
482
				/** @var \OC\Files\Storage\Storage $targetStorage */
483
				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
484
485
				if ($needsPartFile) {
486
					// we first assembly the target file as a part file
487
					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
488
					/** @var \OC\Files\Storage\Storage $targetStorage */
489
					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
490
491
492
					$chunk_handler->file_assemble($partStorage, $partInternalPath);
493
494
					// here is the final atomic rename
495
					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
496
					$fileExists = $targetStorage->file_exists($targetInternalPath);
497
					if ($renameOkay === false || $fileExists === false) {
498
						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
499
						// only delete if an error occurred and the target file was already created
500
						if ($fileExists) {
501
							// set to null to avoid double-deletion when handling exception
502
							// stray part file
503
							$partFile = null;
504
							$targetStorage->unlink($targetInternalPath);
505
						}
506
						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
507
						throw new Exception('Could not rename part file assembled from chunks');
508
					}
509
				} else {
510
					// assemble directly into the final file
511
					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
512
				}
513
514
				// allow sync clients to send the mtime along in a header
515
				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
516
					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
517
					if ($targetStorage->touch($targetInternalPath, $mtime)) {
518
						$this->header('X-OC-MTime: accepted');
519
					}
520
				}
521
522
				// since we skipped the view we need to scan and emit the hooks ourselves
523
				$targetStorage->getUpdater()->update($targetInternalPath);
524
525
				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
526
527
				$this->emitPostHooks($exists, $targetPath);
528
529
				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
530
				$info = $this->fileView->getFileInfo($targetPath);
531
532
				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
533
					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
534
					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
535
				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
536
					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
537
				}
538
539
				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
540
541
				return $info->getEtag();
542
			} catch (\Exception $e) {
543
				if ($partFile !== null) {
544
					$targetStorage->unlink($targetInternalPath);
545
				}
546
				$this->convertToSabreException($e);
547
			}
548
		}
549
550
		return null;
551
	}
552
553
	/**
554
	 * Convert the given exception to a SabreException instance
555
	 *
556
	 * @param \Exception $e
557
	 *
558
	 * @throws \Sabre\DAV\Exception
559
	 */
560
	private function convertToSabreException(\Exception $e) {
561
		if ($e instanceof \Sabre\DAV\Exception) {
562
			throw $e;
563
		}
564
		if ($e instanceof NotPermittedException) {
565
			// a more general case - due to whatever reason the content could not be written
566
			throw new Forbidden($e->getMessage(), 0, $e);
567
		}
568
		if ($e instanceof ForbiddenException) {
569
			// the path for the file was forbidden
570
			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
571
		}
572
		if ($e instanceof EntityTooLargeException) {
573
			// the file is too big to be stored
574
			throw new EntityTooLarge($e->getMessage(), 0, $e);
575
		}
576
		if ($e instanceof InvalidContentException) {
577
			// the file content is not permitted
578
			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
579
		}
580
		if ($e instanceof InvalidPathException) {
581
			// the path for the file was not valid
582
			// TODO: find proper http status code for this case
583
			throw new Forbidden($e->getMessage(), 0, $e);
584
		}
585
		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
586
			// the file is currently being written to by another process
587
			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
588
		}
589
		if ($e instanceof GenericEncryptionException) {
590
			// returning 503 will allow retry of the operation at a later point in time
591
			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
592
		}
593
		if ($e instanceof StorageNotAvailableException) {
594
			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
595
		}
596
		if ($e instanceof NotFoundException) {
597
			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
598
		}
599
600
		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
601
	}
602
603
	/**
604
	 * Get the checksum for this file
605
	 *
606
	 * @return string
607
	 */
608
	public function getChecksum() {
609
		return $this->info->getChecksum();
610
	}
611
612
	protected function header($string) {
613
		\header($string);
614
	}
615
}
616