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