Passed
Push — master ( b21215...d63abe )
by Roeland
13:25 queued 17s
created
apps/dav/lib/Connector/Sabre/File.php 2 patches
Indentation   +609 added lines, -609 removed lines patch added patch discarded remove patch
@@ -71,613 +71,613 @@
 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
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
292
-				} catch (\Exception $e) {
293
-					$partStorage->unlink($internalPartPath);
294
-					$this->convertToSabreException($e);
295
-				}
296
-			}
297
-
298
-			// since we skipped the view we need to scan and emit the hooks ourselves
299
-			$storage->getUpdater()->update($internalPath);
300
-
301
-			try {
302
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
303
-			} catch (LockedException $e) {
304
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
305
-			}
306
-
307
-			// allow sync clients to send the mtime along in a header
308
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
309
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
310
-				if ($this->fileView->touch($this->path, $mtime)) {
311
-					$this->header('X-OC-MTime: accepted');
312
-				}
313
-			}
314
-
315
-			$fileInfoUpdate = [
316
-				'upload_time' => time()
317
-			];
318
-
319
-			// allow sync clients to send the creation time along in a header
320
-			if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
321
-				$ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
322
-				$fileInfoUpdate['creation_time'] = $ctime;
323
-				$this->header('X-OC-CTime: accepted');
324
-			}
325
-
326
-			$this->fileView->putFileInfo($this->path, $fileInfoUpdate);
327
-
328
-			if ($view) {
329
-				$this->emitPostHooks($exists);
330
-			}
331
-
332
-			$this->refreshInfo();
333
-
334
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
335
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
336
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
337
-				$this->refreshInfo();
338
-			} elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
339
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
340
-				$this->refreshInfo();
341
-			}
342
-		} catch (StorageNotAvailableException $e) {
343
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
344
-		}
345
-
346
-		return '"' . $this->info->getEtag() . '"';
347
-	}
348
-
349
-	private function getPartFileBasePath($path) {
350
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
351
-		if ($partFileInStorage) {
352
-			return $path;
353
-		} else {
354
-			return md5($path); // will place it in the root of the view with a unique name
355
-		}
356
-	}
357
-
358
-	/**
359
-	 * @param string $path
360
-	 */
361
-	private function emitPreHooks($exists, $path = null) {
362
-		if (is_null($path)) {
363
-			$path = $this->path;
364
-		}
365
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
366
-		$run = true;
367
-
368
-		if (!$exists) {
369
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
370
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
371
-				\OC\Files\Filesystem::signal_param_run => &$run,
372
-			]);
373
-		} else {
374
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
375
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
376
-				\OC\Files\Filesystem::signal_param_run => &$run,
377
-			]);
378
-		}
379
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
380
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
381
-			\OC\Files\Filesystem::signal_param_run => &$run,
382
-		]);
383
-		return $run;
384
-	}
385
-
386
-	/**
387
-	 * @param string $path
388
-	 */
389
-	private function emitPostHooks($exists, $path = null) {
390
-		if (is_null($path)) {
391
-			$path = $this->path;
392
-		}
393
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
394
-		if (!$exists) {
395
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
396
-				\OC\Files\Filesystem::signal_param_path => $hookPath
397
-			]);
398
-		} else {
399
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
400
-				\OC\Files\Filesystem::signal_param_path => $hookPath
401
-			]);
402
-		}
403
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
404
-			\OC\Files\Filesystem::signal_param_path => $hookPath
405
-		]);
406
-	}
407
-
408
-	/**
409
-	 * Returns the data
410
-	 *
411
-	 * @return resource
412
-	 * @throws Forbidden
413
-	 * @throws ServiceUnavailable
414
-	 */
415
-	public function get() {
416
-		//throw exception if encryption is disabled but files are still encrypted
417
-		try {
418
-			if (!$this->info->isReadable()) {
419
-				// do a if the file did not exist
420
-				throw new NotFound();
421
-			}
422
-			try {
423
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
424
-			} catch (\Exception $e) {
425
-				$this->convertToSabreException($e);
426
-			}
427
-			if ($res === false) {
428
-				throw new ServiceUnavailable("Could not open file");
429
-			}
430
-			return $res;
431
-		} catch (GenericEncryptionException $e) {
432
-			// returning 503 will allow retry of the operation at a later point in time
433
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
434
-		} catch (StorageNotAvailableException $e) {
435
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
436
-		} catch (ForbiddenException $ex) {
437
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
438
-		} catch (LockedException $e) {
439
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
440
-		}
441
-	}
442
-
443
-	/**
444
-	 * Delete the current file
445
-	 *
446
-	 * @throws Forbidden
447
-	 * @throws ServiceUnavailable
448
-	 */
449
-	public function delete() {
450
-		if (!$this->info->isDeletable()) {
451
-			throw new Forbidden();
452
-		}
453
-
454
-		try {
455
-			if (!$this->fileView->unlink($this->path)) {
456
-				// assume it wasn't possible to delete due to permissions
457
-				throw new Forbidden();
458
-			}
459
-		} catch (StorageNotAvailableException $e) {
460
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
461
-		} catch (ForbiddenException $ex) {
462
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
463
-		} catch (LockedException $e) {
464
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
465
-		}
466
-	}
467
-
468
-	/**
469
-	 * Returns the mime-type for a file
470
-	 *
471
-	 * If null is returned, we'll assume application/octet-stream
472
-	 *
473
-	 * @return string
474
-	 */
475
-	public function getContentType() {
476
-		$mimeType = $this->info->getMimetype();
477
-
478
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
479
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
480
-			return $mimeType;
481
-		}
482
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
483
-	}
484
-
485
-	/**
486
-	 * @return array|false
487
-	 */
488
-	public function getDirectDownload() {
489
-		if (\OCP\App::isEnabled('encryption')) {
490
-			return [];
491
-		}
492
-		/** @var \OCP\Files\Storage $storage */
493
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
494
-		if (is_null($storage)) {
495
-			return [];
496
-		}
497
-
498
-		return $storage->getDirectDownload($internalPath);
499
-	}
500
-
501
-	/**
502
-	 * @param resource $data
503
-	 * @return null|string
504
-	 * @throws Exception
505
-	 * @throws BadRequest
506
-	 * @throws NotImplemented
507
-	 * @throws ServiceUnavailable
508
-	 */
509
-	private function createFileChunked($data) {
510
-		list($path, $name) = \Sabre\Uri\split($this->path);
511
-
512
-		$info = \OC_FileChunking::decodeName($name);
513
-		if (empty($info)) {
514
-			throw new NotImplemented('Invalid chunk name');
515
-		}
516
-
517
-		$chunk_handler = new \OC_FileChunking($info);
518
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
519
-
520
-		//detect aborted upload
521
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
522
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
523
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
524
-				if ($bytesWritten !== $expected) {
525
-					$chunk_handler->remove($info['index']);
526
-					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.');
527
-				}
528
-			}
529
-		}
530
-
531
-		if ($chunk_handler->isComplete()) {
532
-			/** @var Storage $storage */
533
-			list($storage,) = $this->fileView->resolvePath($path);
534
-			$needsPartFile = $storage->needsPartFile();
535
-			$partFile = null;
536
-
537
-			$targetPath = $path . '/' . $info['name'];
538
-			/** @var \OC\Files\Storage\Storage $targetStorage */
539
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
540
-
541
-			$exists = $this->fileView->file_exists($targetPath);
542
-
543
-			try {
544
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
545
-
546
-				$this->emitPreHooks($exists, $targetPath);
547
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
548
-				/** @var \OC\Files\Storage\Storage $targetStorage */
549
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
550
-
551
-				if ($needsPartFile) {
552
-					// we first assembly the target file as a part file
553
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
554
-					/** @var \OC\Files\Storage\Storage $targetStorage */
555
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
556
-
557
-
558
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
559
-
560
-					// here is the final atomic rename
561
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
562
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
563
-					if ($renameOkay === false || $fileExists === false) {
564
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
565
-						// only delete if an error occurred and the target file was already created
566
-						if ($fileExists) {
567
-							// set to null to avoid double-deletion when handling exception
568
-							// stray part file
569
-							$partFile = null;
570
-							$targetStorage->unlink($targetInternalPath);
571
-						}
572
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
573
-						throw new Exception('Could not rename part file assembled from chunks');
574
-					}
575
-				} else {
576
-					// assemble directly into the final file
577
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
578
-				}
579
-
580
-				// allow sync clients to send the mtime along in a header
581
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
582
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
583
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
584
-						$this->header('X-OC-MTime: accepted');
585
-					}
586
-				}
587
-
588
-				// since we skipped the view we need to scan and emit the hooks ourselves
589
-				$targetStorage->getUpdater()->update($targetInternalPath);
590
-
591
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
592
-
593
-				$this->emitPostHooks($exists, $targetPath);
594
-
595
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
596
-				$info = $this->fileView->getFileInfo($targetPath);
597
-
598
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
599
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
600
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
601
-				} elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
602
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
603
-				}
604
-
605
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
606
-
607
-				return $info->getEtag();
608
-			} catch (\Exception $e) {
609
-				if ($partFile !== null) {
610
-					$targetStorage->unlink($targetInternalPath);
611
-				}
612
-				$this->convertToSabreException($e);
613
-			}
614
-		}
615
-
616
-		return null;
617
-	}
618
-
619
-	/**
620
-	 * Convert the given exception to a SabreException instance
621
-	 *
622
-	 * @param \Exception $e
623
-	 *
624
-	 * @throws \Sabre\DAV\Exception
625
-	 */
626
-	private function convertToSabreException(\Exception $e) {
627
-		if ($e instanceof \Sabre\DAV\Exception) {
628
-			throw $e;
629
-		}
630
-		if ($e instanceof NotPermittedException) {
631
-			// a more general case - due to whatever reason the content could not be written
632
-			throw new Forbidden($e->getMessage(), 0, $e);
633
-		}
634
-		if ($e instanceof ForbiddenException) {
635
-			// the path for the file was forbidden
636
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
637
-		}
638
-		if ($e instanceof EntityTooLargeException) {
639
-			// the file is too big to be stored
640
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
641
-		}
642
-		if ($e instanceof InvalidContentException) {
643
-			// the file content is not permitted
644
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
645
-		}
646
-		if ($e instanceof InvalidPathException) {
647
-			// the path for the file was not valid
648
-			// TODO: find proper http status code for this case
649
-			throw new Forbidden($e->getMessage(), 0, $e);
650
-		}
651
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
652
-			// the file is currently being written to by another process
653
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
654
-		}
655
-		if ($e instanceof GenericEncryptionException) {
656
-			// returning 503 will allow retry of the operation at a later point in time
657
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
658
-		}
659
-		if ($e instanceof StorageNotAvailableException) {
660
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
661
-		}
662
-		if ($e instanceof NotFoundException) {
663
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
664
-		}
665
-
666
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
667
-	}
668
-
669
-	/**
670
-	 * Get the checksum for this file
671
-	 *
672
-	 * @return string
673
-	 */
674
-	public function getChecksum() {
675
-		return $this->info->getChecksum();
676
-	}
677
-
678
-	protected function header($string) {
679
-		if (!\OC::$CLI) {
680
-			\header($string);
681
-		}
682
-	}
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
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
292
+                } catch (\Exception $e) {
293
+                    $partStorage->unlink($internalPartPath);
294
+                    $this->convertToSabreException($e);
295
+                }
296
+            }
297
+
298
+            // since we skipped the view we need to scan and emit the hooks ourselves
299
+            $storage->getUpdater()->update($internalPath);
300
+
301
+            try {
302
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
303
+            } catch (LockedException $e) {
304
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
305
+            }
306
+
307
+            // allow sync clients to send the mtime along in a header
308
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
309
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
310
+                if ($this->fileView->touch($this->path, $mtime)) {
311
+                    $this->header('X-OC-MTime: accepted');
312
+                }
313
+            }
314
+
315
+            $fileInfoUpdate = [
316
+                'upload_time' => time()
317
+            ];
318
+
319
+            // allow sync clients to send the creation time along in a header
320
+            if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
321
+                $ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
322
+                $fileInfoUpdate['creation_time'] = $ctime;
323
+                $this->header('X-OC-CTime: accepted');
324
+            }
325
+
326
+            $this->fileView->putFileInfo($this->path, $fileInfoUpdate);
327
+
328
+            if ($view) {
329
+                $this->emitPostHooks($exists);
330
+            }
331
+
332
+            $this->refreshInfo();
333
+
334
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
335
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
336
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
337
+                $this->refreshInfo();
338
+            } elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
339
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
340
+                $this->refreshInfo();
341
+            }
342
+        } catch (StorageNotAvailableException $e) {
343
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
344
+        }
345
+
346
+        return '"' . $this->info->getEtag() . '"';
347
+    }
348
+
349
+    private function getPartFileBasePath($path) {
350
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
351
+        if ($partFileInStorage) {
352
+            return $path;
353
+        } else {
354
+            return md5($path); // will place it in the root of the view with a unique name
355
+        }
356
+    }
357
+
358
+    /**
359
+     * @param string $path
360
+     */
361
+    private function emitPreHooks($exists, $path = null) {
362
+        if (is_null($path)) {
363
+            $path = $this->path;
364
+        }
365
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
366
+        $run = true;
367
+
368
+        if (!$exists) {
369
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
370
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
371
+                \OC\Files\Filesystem::signal_param_run => &$run,
372
+            ]);
373
+        } else {
374
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
375
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
376
+                \OC\Files\Filesystem::signal_param_run => &$run,
377
+            ]);
378
+        }
379
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
380
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
381
+            \OC\Files\Filesystem::signal_param_run => &$run,
382
+        ]);
383
+        return $run;
384
+    }
385
+
386
+    /**
387
+     * @param string $path
388
+     */
389
+    private function emitPostHooks($exists, $path = null) {
390
+        if (is_null($path)) {
391
+            $path = $this->path;
392
+        }
393
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
394
+        if (!$exists) {
395
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
396
+                \OC\Files\Filesystem::signal_param_path => $hookPath
397
+            ]);
398
+        } else {
399
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
400
+                \OC\Files\Filesystem::signal_param_path => $hookPath
401
+            ]);
402
+        }
403
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
404
+            \OC\Files\Filesystem::signal_param_path => $hookPath
405
+        ]);
406
+    }
407
+
408
+    /**
409
+     * Returns the data
410
+     *
411
+     * @return resource
412
+     * @throws Forbidden
413
+     * @throws ServiceUnavailable
414
+     */
415
+    public function get() {
416
+        //throw exception if encryption is disabled but files are still encrypted
417
+        try {
418
+            if (!$this->info->isReadable()) {
419
+                // do a if the file did not exist
420
+                throw new NotFound();
421
+            }
422
+            try {
423
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
424
+            } catch (\Exception $e) {
425
+                $this->convertToSabreException($e);
426
+            }
427
+            if ($res === false) {
428
+                throw new ServiceUnavailable("Could not open file");
429
+            }
430
+            return $res;
431
+        } catch (GenericEncryptionException $e) {
432
+            // returning 503 will allow retry of the operation at a later point in time
433
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
434
+        } catch (StorageNotAvailableException $e) {
435
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
436
+        } catch (ForbiddenException $ex) {
437
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
438
+        } catch (LockedException $e) {
439
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
440
+        }
441
+    }
442
+
443
+    /**
444
+     * Delete the current file
445
+     *
446
+     * @throws Forbidden
447
+     * @throws ServiceUnavailable
448
+     */
449
+    public function delete() {
450
+        if (!$this->info->isDeletable()) {
451
+            throw new Forbidden();
452
+        }
453
+
454
+        try {
455
+            if (!$this->fileView->unlink($this->path)) {
456
+                // assume it wasn't possible to delete due to permissions
457
+                throw new Forbidden();
458
+            }
459
+        } catch (StorageNotAvailableException $e) {
460
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
461
+        } catch (ForbiddenException $ex) {
462
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
463
+        } catch (LockedException $e) {
464
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
465
+        }
466
+    }
467
+
468
+    /**
469
+     * Returns the mime-type for a file
470
+     *
471
+     * If null is returned, we'll assume application/octet-stream
472
+     *
473
+     * @return string
474
+     */
475
+    public function getContentType() {
476
+        $mimeType = $this->info->getMimetype();
477
+
478
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
479
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
480
+            return $mimeType;
481
+        }
482
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
483
+    }
484
+
485
+    /**
486
+     * @return array|false
487
+     */
488
+    public function getDirectDownload() {
489
+        if (\OCP\App::isEnabled('encryption')) {
490
+            return [];
491
+        }
492
+        /** @var \OCP\Files\Storage $storage */
493
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
494
+        if (is_null($storage)) {
495
+            return [];
496
+        }
497
+
498
+        return $storage->getDirectDownload($internalPath);
499
+    }
500
+
501
+    /**
502
+     * @param resource $data
503
+     * @return null|string
504
+     * @throws Exception
505
+     * @throws BadRequest
506
+     * @throws NotImplemented
507
+     * @throws ServiceUnavailable
508
+     */
509
+    private function createFileChunked($data) {
510
+        list($path, $name) = \Sabre\Uri\split($this->path);
511
+
512
+        $info = \OC_FileChunking::decodeName($name);
513
+        if (empty($info)) {
514
+            throw new NotImplemented('Invalid chunk name');
515
+        }
516
+
517
+        $chunk_handler = new \OC_FileChunking($info);
518
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
519
+
520
+        //detect aborted upload
521
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
522
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
523
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
524
+                if ($bytesWritten !== $expected) {
525
+                    $chunk_handler->remove($info['index']);
526
+                    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.');
527
+                }
528
+            }
529
+        }
530
+
531
+        if ($chunk_handler->isComplete()) {
532
+            /** @var Storage $storage */
533
+            list($storage,) = $this->fileView->resolvePath($path);
534
+            $needsPartFile = $storage->needsPartFile();
535
+            $partFile = null;
536
+
537
+            $targetPath = $path . '/' . $info['name'];
538
+            /** @var \OC\Files\Storage\Storage $targetStorage */
539
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
540
+
541
+            $exists = $this->fileView->file_exists($targetPath);
542
+
543
+            try {
544
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
545
+
546
+                $this->emitPreHooks($exists, $targetPath);
547
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
548
+                /** @var \OC\Files\Storage\Storage $targetStorage */
549
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
550
+
551
+                if ($needsPartFile) {
552
+                    // we first assembly the target file as a part file
553
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
554
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
555
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
556
+
557
+
558
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
559
+
560
+                    // here is the final atomic rename
561
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
562
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
563
+                    if ($renameOkay === false || $fileExists === false) {
564
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
565
+                        // only delete if an error occurred and the target file was already created
566
+                        if ($fileExists) {
567
+                            // set to null to avoid double-deletion when handling exception
568
+                            // stray part file
569
+                            $partFile = null;
570
+                            $targetStorage->unlink($targetInternalPath);
571
+                        }
572
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
573
+                        throw new Exception('Could not rename part file assembled from chunks');
574
+                    }
575
+                } else {
576
+                    // assemble directly into the final file
577
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
578
+                }
579
+
580
+                // allow sync clients to send the mtime along in a header
581
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
582
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
583
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
584
+                        $this->header('X-OC-MTime: accepted');
585
+                    }
586
+                }
587
+
588
+                // since we skipped the view we need to scan and emit the hooks ourselves
589
+                $targetStorage->getUpdater()->update($targetInternalPath);
590
+
591
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
592
+
593
+                $this->emitPostHooks($exists, $targetPath);
594
+
595
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
596
+                $info = $this->fileView->getFileInfo($targetPath);
597
+
598
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
599
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
600
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
601
+                } elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
602
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
603
+                }
604
+
605
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
606
+
607
+                return $info->getEtag();
608
+            } catch (\Exception $e) {
609
+                if ($partFile !== null) {
610
+                    $targetStorage->unlink($targetInternalPath);
611
+                }
612
+                $this->convertToSabreException($e);
613
+            }
614
+        }
615
+
616
+        return null;
617
+    }
618
+
619
+    /**
620
+     * Convert the given exception to a SabreException instance
621
+     *
622
+     * @param \Exception $e
623
+     *
624
+     * @throws \Sabre\DAV\Exception
625
+     */
626
+    private function convertToSabreException(\Exception $e) {
627
+        if ($e instanceof \Sabre\DAV\Exception) {
628
+            throw $e;
629
+        }
630
+        if ($e instanceof NotPermittedException) {
631
+            // a more general case - due to whatever reason the content could not be written
632
+            throw new Forbidden($e->getMessage(), 0, $e);
633
+        }
634
+        if ($e instanceof ForbiddenException) {
635
+            // the path for the file was forbidden
636
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
637
+        }
638
+        if ($e instanceof EntityTooLargeException) {
639
+            // the file is too big to be stored
640
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
641
+        }
642
+        if ($e instanceof InvalidContentException) {
643
+            // the file content is not permitted
644
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
645
+        }
646
+        if ($e instanceof InvalidPathException) {
647
+            // the path for the file was not valid
648
+            // TODO: find proper http status code for this case
649
+            throw new Forbidden($e->getMessage(), 0, $e);
650
+        }
651
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
652
+            // the file is currently being written to by another process
653
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
654
+        }
655
+        if ($e instanceof GenericEncryptionException) {
656
+            // returning 503 will allow retry of the operation at a later point in time
657
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
658
+        }
659
+        if ($e instanceof StorageNotAvailableException) {
660
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
661
+        }
662
+        if ($e instanceof NotFoundException) {
663
+            throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
664
+        }
665
+
666
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
667
+    }
668
+
669
+    /**
670
+     * Get the checksum for this file
671
+     *
672
+     * @return string
673
+     */
674
+    public function getChecksum() {
675
+        return $this->info->getChecksum();
676
+    }
677
+
678
+    protected function header($string) {
679
+        if (!\OC::$CLI) {
680
+            \header($string);
681
+        }
682
+    }
683 683
 }
Please login to merge, or discard this patch.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
 				throw new Forbidden();
127 127
 			}
128 128
 		} catch (StorageNotAvailableException $e) {
129
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
129
+			throw new ServiceUnavailable("File is not updatable: ".$e->getMessage());
130 130
 		}
131 131
 
132 132
 		// verify path of the target
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
 
150 150
 		if ($needsPartFile) {
151 151
 			// mark file as partial while uploading (ignored by the scanner)
152
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
152
+			$partFilePath = $this->getPartFileBasePath($this->path).'.ocTransferId'.rand().'.part';
153 153
 
154 154
 			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
155 155
 				$needsPartFile = false;
@@ -183,19 +183,19 @@  discard block
 block discarded – undo
183 183
 				$data = $tmpData;
184 184
 			}
185 185
 
186
-			$data = HashWrapper::wrap($data, 'md5', function ($hash) {
187
-				$this->header('X-Hash-MD5: ' . $hash);
186
+			$data = HashWrapper::wrap($data, 'md5', function($hash) {
187
+				$this->header('X-Hash-MD5: '.$hash);
188 188
 			});
189
-			$data = HashWrapper::wrap($data, 'sha1', function ($hash) {
190
-				$this->header('X-Hash-SHA1: ' . $hash);
189
+			$data = HashWrapper::wrap($data, 'sha1', function($hash) {
190
+				$this->header('X-Hash-SHA1: '.$hash);
191 191
 			});
192
-			$data = HashWrapper::wrap($data, 'sha256', function ($hash) {
193
-				$this->header('X-Hash-SHA256: ' . $hash);
192
+			$data = HashWrapper::wrap($data, 'sha256', function($hash) {
193
+				$this->header('X-Hash-SHA256: '.$hash);
194 194
 			});
195 195
 
196 196
 			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
197 197
 				$isEOF = false;
198
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
198
+				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function($stream) use (&$isEOF) {
199 199
 					$isEOF = feof($stream);
200 200
 				});
201 201
 
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 					$expected = $_SERVER['CONTENT_LENGTH'];
226 226
 				}
227 227
 				if ($expected !== "0") {
228
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
228
+					throw new Exception('Error while copying file to target location (copied bytes: '.$count.', expected filesize: '.$expected.' )');
229 229
 				}
230 230
 			}
231 231
 
@@ -233,9 +233,9 @@  discard block
 block discarded – undo
233 233
 			// double check if the file was fully received
234 234
 			// compare expected and actual size
235 235
 			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
236
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
236
+				$expected = (int) $_SERVER['CONTENT_LENGTH'];
237 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.');
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 239
 				}
240 240
 			}
241 241
 		} catch (\Exception $e) {
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
 					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
285 285
 					$fileExists = $storage->file_exists($internalPath);
286 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']);
287
+						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: '.($renameOkay ? 'true' : 'false').', $fileExists: '.($fileExists ? 'true' : 'false').')', ['app' => 'webdav']);
288 288
 						throw new Exception('Could not rename part file to final file');
289 289
 					}
290 290
 				} catch (ForbiddenException $ex) {
@@ -340,10 +340,10 @@  discard block
 block discarded – undo
340 340
 				$this->refreshInfo();
341 341
 			}
342 342
 		} catch (StorageNotAvailableException $e) {
343
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
343
+			throw new ServiceUnavailable("Failed to check file size: ".$e->getMessage(), 0, $e);
344 344
 		}
345 345
 
346
-		return '"' . $this->info->getEtag() . '"';
346
+		return '"'.$this->info->getEtag().'"';
347 347
 	}
348 348
 
349 349
 	private function getPartFileBasePath($path) {
@@ -430,9 +430,9 @@  discard block
 block discarded – undo
430 430
 			return $res;
431 431
 		} catch (GenericEncryptionException $e) {
432 432
 			// returning 503 will allow retry of the operation at a later point in time
433
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
433
+			throw new ServiceUnavailable("Encryption not ready: ".$e->getMessage());
434 434
 		} catch (StorageNotAvailableException $e) {
435
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
435
+			throw new ServiceUnavailable("Failed to open file: ".$e->getMessage());
436 436
 		} catch (ForbiddenException $ex) {
437 437
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
438 438
 		} catch (LockedException $e) {
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 				throw new Forbidden();
458 458
 			}
459 459
 		} catch (StorageNotAvailableException $e) {
460
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
460
+			throw new ServiceUnavailable("Failed to unlink: ".$e->getMessage());
461 461
 		} catch (ForbiddenException $ex) {
462 462
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
463 463
 		} catch (LockedException $e) {
@@ -520,10 +520,10 @@  discard block
 block discarded – undo
520 520
 		//detect aborted upload
521 521
 		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
522 522
 			if (isset($_SERVER['CONTENT_LENGTH'])) {
523
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
523
+				$expected = (int) $_SERVER['CONTENT_LENGTH'];
524 524
 				if ($bytesWritten !== $expected) {
525 525
 					$chunk_handler->remove($info['index']);
526
-					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.');
526
+					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.');
527 527
 				}
528 528
 			}
529 529
 		}
@@ -534,7 +534,7 @@  discard block
 block discarded – undo
534 534
 			$needsPartFile = $storage->needsPartFile();
535 535
 			$partFile = null;
536 536
 
537
-			$targetPath = $path . '/' . $info['name'];
537
+			$targetPath = $path.'/'.$info['name'];
538 538
 			/** @var \OC\Files\Storage\Storage $targetStorage */
539 539
 			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
540 540
 
@@ -550,7 +550,7 @@  discard block
 block discarded – undo
550 550
 
551 551
 				if ($needsPartFile) {
552 552
 					// we first assembly the target file as a part file
553
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
553
+					$partFile = $this->getPartFileBasePath($path.'/'.$info['name']).'.ocTransferId'.$info['transferid'].'.part';
554 554
 					/** @var \OC\Files\Storage\Storage $targetStorage */
555 555
 					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
556 556
 
@@ -654,13 +654,13 @@  discard block
 block discarded – undo
654 654
 		}
655 655
 		if ($e instanceof GenericEncryptionException) {
656 656
 			// returning 503 will allow retry of the operation at a later point in time
657
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
657
+			throw new ServiceUnavailable('Encryption not ready: '.$e->getMessage(), 0, $e);
658 658
 		}
659 659
 		if ($e instanceof StorageNotAvailableException) {
660
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
660
+			throw new ServiceUnavailable('Failed to write file contents: '.$e->getMessage(), 0, $e);
661 661
 		}
662 662
 		if ($e instanceof NotFoundException) {
663
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
663
+			throw new NotFound('File not found: '.$e->getMessage(), 0, $e);
664 664
 		}
665 665
 
666 666
 		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
Please login to merge, or discard this patch.
lib/private/Files/Stream/HashWrapper.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -24,49 +24,49 @@
 block discarded – undo
24 24
 use Icewind\Streams\Wrapper;
25 25
 
26 26
 class HashWrapper extends Wrapper {
27
-	protected $callback;
28
-	protected $hash;
27
+    protected $callback;
28
+    protected $hash;
29 29
 
30
-	public static function wrap($source, string $algo, callable $callback) {
31
-		$hash = hash_init($algo);
32
-		$context = stream_context_create([
33
-			'hash' => [
34
-				'source' => $source,
35
-				'callback' => $callback,
36
-				'hash' => $hash,
37
-			],
38
-		]);
39
-		return Wrapper::wrapSource($source, $context, 'hash', self::class);
40
-	}
30
+    public static function wrap($source, string $algo, callable $callback) {
31
+        $hash = hash_init($algo);
32
+        $context = stream_context_create([
33
+            'hash' => [
34
+                'source' => $source,
35
+                'callback' => $callback,
36
+                'hash' => $hash,
37
+            ],
38
+        ]);
39
+        return Wrapper::wrapSource($source, $context, 'hash', self::class);
40
+    }
41 41
 
42
-	protected function open() {
43
-		$context = $this->loadContext('hash');
42
+    protected function open() {
43
+        $context = $this->loadContext('hash');
44 44
 
45
-		$this->callback = $context['callback'];
46
-		$this->hash = $context['hash'];
47
-		return true;
48
-	}
45
+        $this->callback = $context['callback'];
46
+        $this->hash = $context['hash'];
47
+        return true;
48
+    }
49 49
 
50
-	public function dir_opendir($path, $options) {
51
-		return $this->open();
52
-	}
50
+    public function dir_opendir($path, $options) {
51
+        return $this->open();
52
+    }
53 53
 
54
-	public function stream_open($path, $mode, $options, &$opened_path) {
55
-		return $this->open();
56
-	}
54
+    public function stream_open($path, $mode, $options, &$opened_path) {
55
+        return $this->open();
56
+    }
57 57
 
58
-	public function stream_read($count) {
59
-		$result = parent::stream_read($count);
60
-		hash_update($this->hash, $result);
61
-		return $result;
62
-	}
58
+    public function stream_read($count) {
59
+        $result = parent::stream_read($count);
60
+        hash_update($this->hash, $result);
61
+        return $result;
62
+    }
63 63
 
64
-	public function stream_close() {
65
-		if (is_callable($this->callback)) {
66
-			call_user_func($this->callback, hash_final($this->hash));
67
-			// prevent further calls by potential PHP 7 GC ghosts
68
-			$this->callback = null;
69
-		}
70
-		return parent::stream_close();
71
-	}
64
+    public function stream_close() {
65
+        if (is_callable($this->callback)) {
66
+            call_user_func($this->callback, hash_final($this->hash));
67
+            // prevent further calls by potential PHP 7 GC ghosts
68
+            $this->callback = null;
69
+        }
70
+        return parent::stream_close();
71
+    }
72 72
 }
Please login to merge, or discard this patch.