Passed
Push — master ( a3036a...224073 )
by Roeland
10:22 queued 12s
created
apps/dav/lib/Connector/Sabre/File.php 1 patch
Indentation   +588 added lines, -588 removed lines patch added patch discarded remove patch
@@ -69,592 +69,592 @@
 block discarded – undo
69 69
 
70 70
 class File extends Node implements IFile {
71 71
 
72
-	protected $request;
73
-
74
-	/**
75
-	 * Sets up the node, expects a full path name
76
-	 *
77
-	 * @param \OC\Files\View $view
78
-	 * @param \OCP\Files\FileInfo $info
79
-	 * @param \OCP\Share\IManager $shareManager
80
-	 * @param \OC\AppFramework\Http\Request $request
81
-	 */
82
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
83
-		parent::__construct($view, $info, $shareManager);
84
-
85
-		if (isset($request)) {
86
-			$this->request = $request;
87
-		} else {
88
-			$this->request = \OC::$server->getRequest();
89
-		}
90
-	}
91
-
92
-	/**
93
-	 * Updates the data
94
-	 *
95
-	 * The data argument is a readable stream resource.
96
-	 *
97
-	 * After a successful put operation, you may choose to return an ETag. The
98
-	 * etag must always be surrounded by double-quotes. These quotes must
99
-	 * appear in the actual string you're returning.
100
-	 *
101
-	 * Clients may use the ETag from a PUT request to later on make sure that
102
-	 * when they update the file, the contents haven't changed in the mean
103
-	 * time.
104
-	 *
105
-	 * If you don't plan to store the file byte-by-byte, and you return a
106
-	 * different object on a subsequent GET you are strongly recommended to not
107
-	 * return an ETag, and just return null.
108
-	 *
109
-	 * @param resource $data
110
-	 *
111
-	 * @throws Forbidden
112
-	 * @throws UnsupportedMediaType
113
-	 * @throws BadRequest
114
-	 * @throws Exception
115
-	 * @throws EntityTooLarge
116
-	 * @throws ServiceUnavailable
117
-	 * @throws FileLocked
118
-	 * @return string|null
119
-	 */
120
-	public function put($data) {
121
-		try {
122
-			$exists = $this->fileView->file_exists($this->path);
123
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
124
-				throw new Forbidden();
125
-			}
126
-		} catch (StorageNotAvailableException $e) {
127
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
128
-		}
129
-
130
-		// verify path of the target
131
-		$this->verifyPath();
132
-
133
-		// chunked handling
134
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
135
-			try {
136
-				return $this->createFileChunked($data);
137
-			} catch (\Exception $e) {
138
-				$this->convertToSabreException($e);
139
-			}
140
-		}
141
-
142
-		/** @var Storage $partStorage */
143
-		list($partStorage) = $this->fileView->resolvePath($this->path);
144
-		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
145
-
146
-		$view = \OC\Files\Filesystem::getView();
147
-
148
-		if ($needsPartFile) {
149
-			// mark file as partial while uploading (ignored by the scanner)
150
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
151
-
152
-			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
153
-				$needsPartFile = false;
154
-			}
155
-		}
156
-		if (!$needsPartFile) {
157
-			// upload file directly as the final path
158
-			$partFilePath = $this->path;
159
-
160
-			if ($view && !$this->emitPreHooks($exists)) {
161
-				throw new Exception('Could not write to final file, canceled by hook');
162
-			}
163
-		}
164
-
165
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
166
-		/** @var \OC\Files\Storage\Storage $partStorage */
167
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
168
-		/** @var \OC\Files\Storage\Storage $storage */
169
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
170
-		try {
171
-			if (!$needsPartFile) {
172
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
173
-			}
174
-
175
-			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
176
-
177
-				if (!is_resource($data)) {
178
-					$tmpData = fopen('php://temp', 'r+');
179
-					if ($data !== null) {
180
-						fwrite($tmpData, $data);
181
-						rewind($tmpData);
182
-					}
183
-					$data = $tmpData;
184
-				}
185
-
186
-				$isEOF = false;
187
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
188
-					$isEOF = feof($stream);
189
-				});
190
-
191
-				$count = $partStorage->writeStream($internalPartPath, $wrappedData);
192
-				$result = $count > 0;
193
-
194
-				if ($result === false) {
195
-					$result = $isEOF;
196
-					if (is_resource($wrappedData)) {
197
-						$result = feof($wrappedData);
198
-					}
199
-				}
200
-
201
-			} else {
202
-				$target = $partStorage->fopen($internalPartPath, 'wb');
203
-				if ($target === false) {
204
-					\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
205
-					// because we have no clue about the cause we can only throw back a 500/Internal Server Error
206
-					throw new Exception('Could not write file contents');
207
-				}
208
-				list($count, $result) = \OC_Helper::streamCopy($data, $target);
209
-				fclose($target);
210
-			}
211
-
212
-			if ($result === false) {
213
-				$expected = -1;
214
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
215
-					$expected = $_SERVER['CONTENT_LENGTH'];
216
-				}
217
-				if ($expected !== "0") {
218
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
219
-				}
220
-			}
221
-
222
-			// if content length is sent by client:
223
-			// double check if the file was fully received
224
-			// compare expected and actual size
225
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
226
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
227
-				if ($count !== $expected) {
228
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
229
-				}
230
-			}
231
-
232
-		} catch (\Exception $e) {
233
-			$context = [];
234
-
235
-			if ($e instanceof LockedException) {
236
-				$context['level'] = ILogger::DEBUG;
237
-			}
238
-
239
-			\OC::$server->getLogger()->logException($e, $context);
240
-			if ($needsPartFile) {
241
-				$partStorage->unlink($internalPartPath);
242
-			}
243
-			$this->convertToSabreException($e);
244
-		}
245
-
246
-		try {
247
-			if ($needsPartFile) {
248
-				if ($view && !$this->emitPreHooks($exists)) {
249
-					$partStorage->unlink($internalPartPath);
250
-					throw new Exception('Could not rename part file to final file, canceled by hook');
251
-				}
252
-				try {
253
-					$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
254
-				} catch (LockedException $e) {
255
-					// during very large uploads, the shared lock we got at the start might have been expired
256
-					// meaning that the above lock can fail not just only because somebody else got a shared lock
257
-					// or because there is no existing shared lock to make exclusive
258
-					//
259
-					// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
260
-					// lock this will still fail, if our original shared lock expired the new lock will be successful and
261
-					// the entire operation will be safe
262
-
263
-					try {
264
-						$this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
265
-					} catch (LockedException $e) {
266
-						if ($needsPartFile) {
267
-							$partStorage->unlink($internalPartPath);
268
-						}
269
-						throw new FileLocked($e->getMessage(), $e->getCode(), $e);
270
-					}
271
-				}
272
-
273
-				// rename to correct path
274
-				try {
275
-					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
276
-					$fileExists = $storage->file_exists($internalPath);
277
-					if ($renameOkay === false || $fileExists === false) {
278
-						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
279
-						throw new Exception('Could not rename part file to final file');
280
-					}
281
-				} catch (ForbiddenException $ex) {
282
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
283
-				} catch (\Exception $e) {
284
-					$partStorage->unlink($internalPartPath);
285
-					$this->convertToSabreException($e);
286
-				}
287
-			}
288
-
289
-			// since we skipped the view we need to scan and emit the hooks ourselves
290
-			$storage->getUpdater()->update($internalPath);
291
-
292
-			try {
293
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
294
-			} catch (LockedException $e) {
295
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
296
-			}
297
-
298
-			// allow sync clients to send the mtime along in a header
299
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
300
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
301
-				if ($this->fileView->touch($this->path, $mtime)) {
302
-					$this->header('X-OC-MTime: accepted');
303
-				}
304
-			}
305
-
306
-			if ($view) {
307
-				$this->emitPostHooks($exists);
308
-			}
309
-
310
-			$this->refreshInfo();
311
-
312
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
313
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
314
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
315
-				$this->refreshInfo();
316
-			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
317
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
318
-				$this->refreshInfo();
319
-			}
320
-
321
-		} catch (StorageNotAvailableException $e) {
322
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
323
-		}
324
-
325
-		return '"' . $this->info->getEtag() . '"';
326
-	}
327
-
328
-	private function getPartFileBasePath($path) {
329
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
330
-		if ($partFileInStorage) {
331
-			return $path;
332
-		} else {
333
-			return md5($path); // will place it in the root of the view with a unique name
334
-		}
335
-	}
336
-
337
-	/**
338
-	 * @param string $path
339
-	 */
340
-	private function emitPreHooks($exists, $path = null) {
341
-		if (is_null($path)) {
342
-			$path = $this->path;
343
-		}
344
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
345
-		$run = true;
346
-
347
-		if (!$exists) {
348
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
349
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
350
-				\OC\Files\Filesystem::signal_param_run => &$run,
351
-			));
352
-		} else {
353
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
354
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
355
-				\OC\Files\Filesystem::signal_param_run => &$run,
356
-			));
357
-		}
358
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
359
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
360
-			\OC\Files\Filesystem::signal_param_run => &$run,
361
-		));
362
-		return $run;
363
-	}
364
-
365
-	/**
366
-	 * @param string $path
367
-	 */
368
-	private function emitPostHooks($exists, $path = null) {
369
-		if (is_null($path)) {
370
-			$path = $this->path;
371
-		}
372
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
373
-		if (!$exists) {
374
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
375
-				\OC\Files\Filesystem::signal_param_path => $hookPath
376
-			));
377
-		} else {
378
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
379
-				\OC\Files\Filesystem::signal_param_path => $hookPath
380
-			));
381
-		}
382
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
383
-			\OC\Files\Filesystem::signal_param_path => $hookPath
384
-		));
385
-	}
386
-
387
-	/**
388
-	 * Returns the data
389
-	 *
390
-	 * @return resource
391
-	 * @throws Forbidden
392
-	 * @throws ServiceUnavailable
393
-	 */
394
-	public function get() {
395
-		//throw exception if encryption is disabled but files are still encrypted
396
-		try {
397
-			if (!$this->info->isReadable()) {
398
-				// do a if the file did not exist
399
-				throw new NotFound();
400
-			}
401
-			try {
402
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
403
-			} catch (\Exception $e) {
404
-				$this->convertToSabreException($e);
405
-			}
406
-			if ($res === false) {
407
-				throw new ServiceUnavailable("Could not open file");
408
-			}
409
-			return $res;
410
-		} catch (GenericEncryptionException $e) {
411
-			// returning 503 will allow retry of the operation at a later point in time
412
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
413
-		} catch (StorageNotAvailableException $e) {
414
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
415
-		} catch (ForbiddenException $ex) {
416
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
417
-		} catch (LockedException $e) {
418
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
419
-		}
420
-	}
421
-
422
-	/**
423
-	 * Delete the current file
424
-	 *
425
-	 * @throws Forbidden
426
-	 * @throws ServiceUnavailable
427
-	 */
428
-	public function delete() {
429
-		if (!$this->info->isDeletable()) {
430
-			throw new Forbidden();
431
-		}
432
-
433
-		try {
434
-			if (!$this->fileView->unlink($this->path)) {
435
-				// assume it wasn't possible to delete due to permissions
436
-				throw new Forbidden();
437
-			}
438
-		} catch (StorageNotAvailableException $e) {
439
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
440
-		} catch (ForbiddenException $ex) {
441
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
442
-		} catch (LockedException $e) {
443
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
444
-		}
445
-	}
446
-
447
-	/**
448
-	 * Returns the mime-type for a file
449
-	 *
450
-	 * If null is returned, we'll assume application/octet-stream
451
-	 *
452
-	 * @return string
453
-	 */
454
-	public function getContentType() {
455
-		$mimeType = $this->info->getMimetype();
456
-
457
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
458
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
459
-			return $mimeType;
460
-		}
461
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
462
-	}
463
-
464
-	/**
465
-	 * @return array|false
466
-	 */
467
-	public function getDirectDownload() {
468
-		if (\OCP\App::isEnabled('encryption')) {
469
-			return [];
470
-		}
471
-		/** @var \OCP\Files\Storage $storage */
472
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
473
-		if (is_null($storage)) {
474
-			return [];
475
-		}
476
-
477
-		return $storage->getDirectDownload($internalPath);
478
-	}
479
-
480
-	/**
481
-	 * @param resource $data
482
-	 * @return null|string
483
-	 * @throws Exception
484
-	 * @throws BadRequest
485
-	 * @throws NotImplemented
486
-	 * @throws ServiceUnavailable
487
-	 */
488
-	private function createFileChunked($data) {
489
-		list($path, $name) = \Sabre\Uri\split($this->path);
490
-
491
-		$info = \OC_FileChunking::decodeName($name);
492
-		if (empty($info)) {
493
-			throw new NotImplemented('Invalid chunk name');
494
-		}
495
-
496
-		$chunk_handler = new \OC_FileChunking($info);
497
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
498
-
499
-		//detect aborted upload
500
-		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
501
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
502
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
503
-				if ($bytesWritten !== $expected) {
504
-					$chunk_handler->remove($info['index']);
505
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
506
-				}
507
-			}
508
-		}
509
-
510
-		if ($chunk_handler->isComplete()) {
511
-			/** @var Storage $storage */
512
-			list($storage,) = $this->fileView->resolvePath($path);
513
-			$needsPartFile = $storage->needsPartFile();
514
-			$partFile = null;
515
-
516
-			$targetPath = $path . '/' . $info['name'];
517
-			/** @var \OC\Files\Storage\Storage $targetStorage */
518
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
519
-
520
-			$exists = $this->fileView->file_exists($targetPath);
521
-
522
-			try {
523
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
524
-
525
-				$this->emitPreHooks($exists, $targetPath);
526
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
527
-				/** @var \OC\Files\Storage\Storage $targetStorage */
528
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
529
-
530
-				if ($needsPartFile) {
531
-					// we first assembly the target file as a part file
532
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
533
-					/** @var \OC\Files\Storage\Storage $targetStorage */
534
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
535
-
536
-
537
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
538
-
539
-					// here is the final atomic rename
540
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
541
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
542
-					if ($renameOkay === false || $fileExists === false) {
543
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
544
-						// only delete if an error occurred and the target file was already created
545
-						if ($fileExists) {
546
-							// set to null to avoid double-deletion when handling exception
547
-							// stray part file
548
-							$partFile = null;
549
-							$targetStorage->unlink($targetInternalPath);
550
-						}
551
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
552
-						throw new Exception('Could not rename part file assembled from chunks');
553
-					}
554
-				} else {
555
-					// assemble directly into the final file
556
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
557
-				}
558
-
559
-				// allow sync clients to send the mtime along in a header
560
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
561
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
562
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
563
-						$this->header('X-OC-MTime: accepted');
564
-					}
565
-				}
566
-
567
-				// since we skipped the view we need to scan and emit the hooks ourselves
568
-				$targetStorage->getUpdater()->update($targetInternalPath);
569
-
570
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
571
-
572
-				$this->emitPostHooks($exists, $targetPath);
573
-
574
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
575
-				$info = $this->fileView->getFileInfo($targetPath);
576
-
577
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
578
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
579
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
580
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
581
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
582
-				}
583
-
584
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
585
-
586
-				return $info->getEtag();
587
-			} catch (\Exception $e) {
588
-				if ($partFile !== null) {
589
-					$targetStorage->unlink($targetInternalPath);
590
-				}
591
-				$this->convertToSabreException($e);
592
-			}
593
-		}
594
-
595
-		return null;
596
-	}
597
-
598
-	/**
599
-	 * Convert the given exception to a SabreException instance
600
-	 *
601
-	 * @param \Exception $e
602
-	 *
603
-	 * @throws \Sabre\DAV\Exception
604
-	 */
605
-	private function convertToSabreException(\Exception $e) {
606
-		if ($e instanceof \Sabre\DAV\Exception) {
607
-			throw $e;
608
-		}
609
-		if ($e instanceof NotPermittedException) {
610
-			// a more general case - due to whatever reason the content could not be written
611
-			throw new Forbidden($e->getMessage(), 0, $e);
612
-		}
613
-		if ($e instanceof ForbiddenException) {
614
-			// the path for the file was forbidden
615
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
616
-		}
617
-		if ($e instanceof EntityTooLargeException) {
618
-			// the file is too big to be stored
619
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
620
-		}
621
-		if ($e instanceof InvalidContentException) {
622
-			// the file content is not permitted
623
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
624
-		}
625
-		if ($e instanceof InvalidPathException) {
626
-			// the path for the file was not valid
627
-			// TODO: find proper http status code for this case
628
-			throw new Forbidden($e->getMessage(), 0, $e);
629
-		}
630
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
631
-			// the file is currently being written to by another process
632
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
633
-		}
634
-		if ($e instanceof GenericEncryptionException) {
635
-			// returning 503 will allow retry of the operation at a later point in time
636
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
637
-		}
638
-		if ($e instanceof StorageNotAvailableException) {
639
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
640
-		}
641
-		if ($e instanceof NotFoundException) {
642
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
643
-		}
644
-
645
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
646
-	}
647
-
648
-	/**
649
-	 * Get the checksum for this file
650
-	 *
651
-	 * @return string
652
-	 */
653
-	public function getChecksum() {
654
-		return $this->info->getChecksum();
655
-	}
656
-
657
-	protected function header($string) {
658
-		\header($string);
659
-	}
72
+    protected $request;
73
+
74
+    /**
75
+     * Sets up the node, expects a full path name
76
+     *
77
+     * @param \OC\Files\View $view
78
+     * @param \OCP\Files\FileInfo $info
79
+     * @param \OCP\Share\IManager $shareManager
80
+     * @param \OC\AppFramework\Http\Request $request
81
+     */
82
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
83
+        parent::__construct($view, $info, $shareManager);
84
+
85
+        if (isset($request)) {
86
+            $this->request = $request;
87
+        } else {
88
+            $this->request = \OC::$server->getRequest();
89
+        }
90
+    }
91
+
92
+    /**
93
+     * Updates the data
94
+     *
95
+     * The data argument is a readable stream resource.
96
+     *
97
+     * After a successful put operation, you may choose to return an ETag. The
98
+     * etag must always be surrounded by double-quotes. These quotes must
99
+     * appear in the actual string you're returning.
100
+     *
101
+     * Clients may use the ETag from a PUT request to later on make sure that
102
+     * when they update the file, the contents haven't changed in the mean
103
+     * time.
104
+     *
105
+     * If you don't plan to store the file byte-by-byte, and you return a
106
+     * different object on a subsequent GET you are strongly recommended to not
107
+     * return an ETag, and just return null.
108
+     *
109
+     * @param resource $data
110
+     *
111
+     * @throws Forbidden
112
+     * @throws UnsupportedMediaType
113
+     * @throws BadRequest
114
+     * @throws Exception
115
+     * @throws EntityTooLarge
116
+     * @throws ServiceUnavailable
117
+     * @throws FileLocked
118
+     * @return string|null
119
+     */
120
+    public function put($data) {
121
+        try {
122
+            $exists = $this->fileView->file_exists($this->path);
123
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
124
+                throw new Forbidden();
125
+            }
126
+        } catch (StorageNotAvailableException $e) {
127
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
128
+        }
129
+
130
+        // verify path of the target
131
+        $this->verifyPath();
132
+
133
+        // chunked handling
134
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
135
+            try {
136
+                return $this->createFileChunked($data);
137
+            } catch (\Exception $e) {
138
+                $this->convertToSabreException($e);
139
+            }
140
+        }
141
+
142
+        /** @var Storage $partStorage */
143
+        list($partStorage) = $this->fileView->resolvePath($this->path);
144
+        $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
145
+
146
+        $view = \OC\Files\Filesystem::getView();
147
+
148
+        if ($needsPartFile) {
149
+            // mark file as partial while uploading (ignored by the scanner)
150
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
151
+
152
+            if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
153
+                $needsPartFile = false;
154
+            }
155
+        }
156
+        if (!$needsPartFile) {
157
+            // upload file directly as the final path
158
+            $partFilePath = $this->path;
159
+
160
+            if ($view && !$this->emitPreHooks($exists)) {
161
+                throw new Exception('Could not write to final file, canceled by hook');
162
+            }
163
+        }
164
+
165
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
166
+        /** @var \OC\Files\Storage\Storage $partStorage */
167
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
168
+        /** @var \OC\Files\Storage\Storage $storage */
169
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
170
+        try {
171
+            if (!$needsPartFile) {
172
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
173
+            }
174
+
175
+            if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
176
+
177
+                if (!is_resource($data)) {
178
+                    $tmpData = fopen('php://temp', 'r+');
179
+                    if ($data !== null) {
180
+                        fwrite($tmpData, $data);
181
+                        rewind($tmpData);
182
+                    }
183
+                    $data = $tmpData;
184
+                }
185
+
186
+                $isEOF = false;
187
+                $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
188
+                    $isEOF = feof($stream);
189
+                });
190
+
191
+                $count = $partStorage->writeStream($internalPartPath, $wrappedData);
192
+                $result = $count > 0;
193
+
194
+                if ($result === false) {
195
+                    $result = $isEOF;
196
+                    if (is_resource($wrappedData)) {
197
+                        $result = feof($wrappedData);
198
+                    }
199
+                }
200
+
201
+            } else {
202
+                $target = $partStorage->fopen($internalPartPath, 'wb');
203
+                if ($target === false) {
204
+                    \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
205
+                    // because we have no clue about the cause we can only throw back a 500/Internal Server Error
206
+                    throw new Exception('Could not write file contents');
207
+                }
208
+                list($count, $result) = \OC_Helper::streamCopy($data, $target);
209
+                fclose($target);
210
+            }
211
+
212
+            if ($result === false) {
213
+                $expected = -1;
214
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
215
+                    $expected = $_SERVER['CONTENT_LENGTH'];
216
+                }
217
+                if ($expected !== "0") {
218
+                    throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
219
+                }
220
+            }
221
+
222
+            // if content length is sent by client:
223
+            // double check if the file was fully received
224
+            // compare expected and actual size
225
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
226
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
227
+                if ($count !== $expected) {
228
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
229
+                }
230
+            }
231
+
232
+        } catch (\Exception $e) {
233
+            $context = [];
234
+
235
+            if ($e instanceof LockedException) {
236
+                $context['level'] = ILogger::DEBUG;
237
+            }
238
+
239
+            \OC::$server->getLogger()->logException($e, $context);
240
+            if ($needsPartFile) {
241
+                $partStorage->unlink($internalPartPath);
242
+            }
243
+            $this->convertToSabreException($e);
244
+        }
245
+
246
+        try {
247
+            if ($needsPartFile) {
248
+                if ($view && !$this->emitPreHooks($exists)) {
249
+                    $partStorage->unlink($internalPartPath);
250
+                    throw new Exception('Could not rename part file to final file, canceled by hook');
251
+                }
252
+                try {
253
+                    $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
254
+                } catch (LockedException $e) {
255
+                    // during very large uploads, the shared lock we got at the start might have been expired
256
+                    // meaning that the above lock can fail not just only because somebody else got a shared lock
257
+                    // or because there is no existing shared lock to make exclusive
258
+                    //
259
+                    // Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
260
+                    // lock this will still fail, if our original shared lock expired the new lock will be successful and
261
+                    // the entire operation will be safe
262
+
263
+                    try {
264
+                        $this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
265
+                    } catch (LockedException $e) {
266
+                        if ($needsPartFile) {
267
+                            $partStorage->unlink($internalPartPath);
268
+                        }
269
+                        throw new FileLocked($e->getMessage(), $e->getCode(), $e);
270
+                    }
271
+                }
272
+
273
+                // rename to correct path
274
+                try {
275
+                    $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
276
+                    $fileExists = $storage->file_exists($internalPath);
277
+                    if ($renameOkay === false || $fileExists === false) {
278
+                        \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
279
+                        throw new Exception('Could not rename part file to final file');
280
+                    }
281
+                } catch (ForbiddenException $ex) {
282
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
283
+                } catch (\Exception $e) {
284
+                    $partStorage->unlink($internalPartPath);
285
+                    $this->convertToSabreException($e);
286
+                }
287
+            }
288
+
289
+            // since we skipped the view we need to scan and emit the hooks ourselves
290
+            $storage->getUpdater()->update($internalPath);
291
+
292
+            try {
293
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
294
+            } catch (LockedException $e) {
295
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
296
+            }
297
+
298
+            // allow sync clients to send the mtime along in a header
299
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
300
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
301
+                if ($this->fileView->touch($this->path, $mtime)) {
302
+                    $this->header('X-OC-MTime: accepted');
303
+                }
304
+            }
305
+
306
+            if ($view) {
307
+                $this->emitPostHooks($exists);
308
+            }
309
+
310
+            $this->refreshInfo();
311
+
312
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
313
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
314
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
315
+                $this->refreshInfo();
316
+            } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
317
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
318
+                $this->refreshInfo();
319
+            }
320
+
321
+        } catch (StorageNotAvailableException $e) {
322
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
323
+        }
324
+
325
+        return '"' . $this->info->getEtag() . '"';
326
+    }
327
+
328
+    private function getPartFileBasePath($path) {
329
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
330
+        if ($partFileInStorage) {
331
+            return $path;
332
+        } else {
333
+            return md5($path); // will place it in the root of the view with a unique name
334
+        }
335
+    }
336
+
337
+    /**
338
+     * @param string $path
339
+     */
340
+    private function emitPreHooks($exists, $path = null) {
341
+        if (is_null($path)) {
342
+            $path = $this->path;
343
+        }
344
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
345
+        $run = true;
346
+
347
+        if (!$exists) {
348
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
349
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
350
+                \OC\Files\Filesystem::signal_param_run => &$run,
351
+            ));
352
+        } else {
353
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
354
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
355
+                \OC\Files\Filesystem::signal_param_run => &$run,
356
+            ));
357
+        }
358
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
359
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
360
+            \OC\Files\Filesystem::signal_param_run => &$run,
361
+        ));
362
+        return $run;
363
+    }
364
+
365
+    /**
366
+     * @param string $path
367
+     */
368
+    private function emitPostHooks($exists, $path = null) {
369
+        if (is_null($path)) {
370
+            $path = $this->path;
371
+        }
372
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
373
+        if (!$exists) {
374
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
375
+                \OC\Files\Filesystem::signal_param_path => $hookPath
376
+            ));
377
+        } else {
378
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
379
+                \OC\Files\Filesystem::signal_param_path => $hookPath
380
+            ));
381
+        }
382
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
383
+            \OC\Files\Filesystem::signal_param_path => $hookPath
384
+        ));
385
+    }
386
+
387
+    /**
388
+     * Returns the data
389
+     *
390
+     * @return resource
391
+     * @throws Forbidden
392
+     * @throws ServiceUnavailable
393
+     */
394
+    public function get() {
395
+        //throw exception if encryption is disabled but files are still encrypted
396
+        try {
397
+            if (!$this->info->isReadable()) {
398
+                // do a if the file did not exist
399
+                throw new NotFound();
400
+            }
401
+            try {
402
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
403
+            } catch (\Exception $e) {
404
+                $this->convertToSabreException($e);
405
+            }
406
+            if ($res === false) {
407
+                throw new ServiceUnavailable("Could not open file");
408
+            }
409
+            return $res;
410
+        } catch (GenericEncryptionException $e) {
411
+            // returning 503 will allow retry of the operation at a later point in time
412
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
413
+        } catch (StorageNotAvailableException $e) {
414
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
415
+        } catch (ForbiddenException $ex) {
416
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
417
+        } catch (LockedException $e) {
418
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
419
+        }
420
+    }
421
+
422
+    /**
423
+     * Delete the current file
424
+     *
425
+     * @throws Forbidden
426
+     * @throws ServiceUnavailable
427
+     */
428
+    public function delete() {
429
+        if (!$this->info->isDeletable()) {
430
+            throw new Forbidden();
431
+        }
432
+
433
+        try {
434
+            if (!$this->fileView->unlink($this->path)) {
435
+                // assume it wasn't possible to delete due to permissions
436
+                throw new Forbidden();
437
+            }
438
+        } catch (StorageNotAvailableException $e) {
439
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
440
+        } catch (ForbiddenException $ex) {
441
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
442
+        } catch (LockedException $e) {
443
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
444
+        }
445
+    }
446
+
447
+    /**
448
+     * Returns the mime-type for a file
449
+     *
450
+     * If null is returned, we'll assume application/octet-stream
451
+     *
452
+     * @return string
453
+     */
454
+    public function getContentType() {
455
+        $mimeType = $this->info->getMimetype();
456
+
457
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
458
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
459
+            return $mimeType;
460
+        }
461
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
462
+    }
463
+
464
+    /**
465
+     * @return array|false
466
+     */
467
+    public function getDirectDownload() {
468
+        if (\OCP\App::isEnabled('encryption')) {
469
+            return [];
470
+        }
471
+        /** @var \OCP\Files\Storage $storage */
472
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
473
+        if (is_null($storage)) {
474
+            return [];
475
+        }
476
+
477
+        return $storage->getDirectDownload($internalPath);
478
+    }
479
+
480
+    /**
481
+     * @param resource $data
482
+     * @return null|string
483
+     * @throws Exception
484
+     * @throws BadRequest
485
+     * @throws NotImplemented
486
+     * @throws ServiceUnavailable
487
+     */
488
+    private function createFileChunked($data) {
489
+        list($path, $name) = \Sabre\Uri\split($this->path);
490
+
491
+        $info = \OC_FileChunking::decodeName($name);
492
+        if (empty($info)) {
493
+            throw new NotImplemented('Invalid chunk name');
494
+        }
495
+
496
+        $chunk_handler = new \OC_FileChunking($info);
497
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
498
+
499
+        //detect aborted upload
500
+        if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
501
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
502
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
503
+                if ($bytesWritten !== $expected) {
504
+                    $chunk_handler->remove($info['index']);
505
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
506
+                }
507
+            }
508
+        }
509
+
510
+        if ($chunk_handler->isComplete()) {
511
+            /** @var Storage $storage */
512
+            list($storage,) = $this->fileView->resolvePath($path);
513
+            $needsPartFile = $storage->needsPartFile();
514
+            $partFile = null;
515
+
516
+            $targetPath = $path . '/' . $info['name'];
517
+            /** @var \OC\Files\Storage\Storage $targetStorage */
518
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
519
+
520
+            $exists = $this->fileView->file_exists($targetPath);
521
+
522
+            try {
523
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
524
+
525
+                $this->emitPreHooks($exists, $targetPath);
526
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
527
+                /** @var \OC\Files\Storage\Storage $targetStorage */
528
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
529
+
530
+                if ($needsPartFile) {
531
+                    // we first assembly the target file as a part file
532
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
533
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
534
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
535
+
536
+
537
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
538
+
539
+                    // here is the final atomic rename
540
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
541
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
542
+                    if ($renameOkay === false || $fileExists === false) {
543
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
544
+                        // only delete if an error occurred and the target file was already created
545
+                        if ($fileExists) {
546
+                            // set to null to avoid double-deletion when handling exception
547
+                            // stray part file
548
+                            $partFile = null;
549
+                            $targetStorage->unlink($targetInternalPath);
550
+                        }
551
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
552
+                        throw new Exception('Could not rename part file assembled from chunks');
553
+                    }
554
+                } else {
555
+                    // assemble directly into the final file
556
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
557
+                }
558
+
559
+                // allow sync clients to send the mtime along in a header
560
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
561
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
562
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
563
+                        $this->header('X-OC-MTime: accepted');
564
+                    }
565
+                }
566
+
567
+                // since we skipped the view we need to scan and emit the hooks ourselves
568
+                $targetStorage->getUpdater()->update($targetInternalPath);
569
+
570
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
571
+
572
+                $this->emitPostHooks($exists, $targetPath);
573
+
574
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
575
+                $info = $this->fileView->getFileInfo($targetPath);
576
+
577
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
578
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
579
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
580
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
581
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
582
+                }
583
+
584
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
585
+
586
+                return $info->getEtag();
587
+            } catch (\Exception $e) {
588
+                if ($partFile !== null) {
589
+                    $targetStorage->unlink($targetInternalPath);
590
+                }
591
+                $this->convertToSabreException($e);
592
+            }
593
+        }
594
+
595
+        return null;
596
+    }
597
+
598
+    /**
599
+     * Convert the given exception to a SabreException instance
600
+     *
601
+     * @param \Exception $e
602
+     *
603
+     * @throws \Sabre\DAV\Exception
604
+     */
605
+    private function convertToSabreException(\Exception $e) {
606
+        if ($e instanceof \Sabre\DAV\Exception) {
607
+            throw $e;
608
+        }
609
+        if ($e instanceof NotPermittedException) {
610
+            // a more general case - due to whatever reason the content could not be written
611
+            throw new Forbidden($e->getMessage(), 0, $e);
612
+        }
613
+        if ($e instanceof ForbiddenException) {
614
+            // the path for the file was forbidden
615
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
616
+        }
617
+        if ($e instanceof EntityTooLargeException) {
618
+            // the file is too big to be stored
619
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
620
+        }
621
+        if ($e instanceof InvalidContentException) {
622
+            // the file content is not permitted
623
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
624
+        }
625
+        if ($e instanceof InvalidPathException) {
626
+            // the path for the file was not valid
627
+            // TODO: find proper http status code for this case
628
+            throw new Forbidden($e->getMessage(), 0, $e);
629
+        }
630
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
631
+            // the file is currently being written to by another process
632
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
633
+        }
634
+        if ($e instanceof GenericEncryptionException) {
635
+            // returning 503 will allow retry of the operation at a later point in time
636
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
637
+        }
638
+        if ($e instanceof StorageNotAvailableException) {
639
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
640
+        }
641
+        if ($e instanceof NotFoundException) {
642
+            throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
643
+        }
644
+
645
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
646
+    }
647
+
648
+    /**
649
+     * Get the checksum for this file
650
+     *
651
+     * @return string
652
+     */
653
+    public function getChecksum() {
654
+        return $this->info->getChecksum();
655
+    }
656
+
657
+    protected function header($string) {
658
+        \header($string);
659
+    }
660 660
 }
Please login to merge, or discard this patch.