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